The document provides an overview of the EntityManager class in DevForce, which acts as a gateway between the client and server. It handles communication with the application server through queries and saves. The EntityManager caches entities locally and supports a variety of query types to retrieve entities from the server or cache. It saves changed entities to the server and updates the local cache accordingly.
Download as DOCX, PDF, TXT or read online on Scribd
0 ratings0% found this document useful (0 votes)
719 views
Devforce Reference
The document provides an overview of the EntityManager class in DevForce, which acts as a gateway between the client and server. It handles communication with the application server through queries and saves. The EntityManager caches entities locally and supports a variety of query types to retrieve entities from the server or cache. It saves changed entities to the server and updates the local cache accordingly.
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 759
EntityManager
Last modified on March 23, 2011 22:14
Contents EntityManager overview What next? The EntityManager is the client gateway to the server. The EntityManager typically handles all client communications with the application server. Queries and saves are the most common operations. The EntityManager is the most heavily used DevForce class and its presence is felt on almost every page of the documentation. This topic offers a technical introduction to the EntityManager: how to create one, how to use it, and the capabilities it offers.
EntityManager overview The EntityManager serves three main functions: 1. Communicate with the application server 2. Query and save entities 3. Hold entities in a local container called the entity cache. When the client code of a DevForce application requires data or other external services, it typically calls a member of an EntityManager object. Nothing prevents you from calling external services directly. But it is usually easier and safer to route those requests through an EntityManager. The EntityManager (assisted by helper components) establishes communication channels, sets up the client's security context, serializes and deserializes data, and regulates the application-level flow of traffic with the application server. The EntityServer is its correspondent on the application server. The EntityServer screens and interprets requests coming from EntityManagers, translating them into the appropriate operations on the server. Query and save requests are the predominant EntityManager activities. The EntityManager can login, logout, connect, disconnect, and invoke selected methods for execution on the server. But mostly it trades in queries and saves and the entity data exchanged in those two operations. You can ask the EntityManager to query for entities in a wide variety of ways: with LINQ queries o by EntityKey
with Entity SQL (ESQL) with stored procedure queries by navigating from one entity to another (e.g., aCustomer.Orders) However you do it, EntityManager puts the queried entity results in its entity cache. When you create new entities, you add them to that cache. When you delete entities, you are actually marking entities in the cache, scheduling them to be deleted. The inner workings of changing entities in cache is covered under Create, modify and delete. When you eventually ask the EntityManager to save, it finds the changed entities in cache - the ones you've modified, added, and scheduled for deletion - and sends their data to the EntityServer. If all goes well, the EntityServer reports success and the EntityManager adjusts the cached entities to reflect the save by discarding deleted entities and re-setting the EntityState of the added and modified entities to Unchanged.
EntityManager by task This page categories the public API of the EntityManager class by task type. The types of tasks largely parallel the high-level organization seen in the DevForce development tree-view.
This topic strives to be a birds-eye view of the EntityManager API from the perspective of some trying to grasp the scope and purpose of this, the most important and largest class in DevForce. To that end, the API is presented in a task-oriented fashion, with API member categorized into major functional areas. For example, you will find the query-oriented members gathered together under the "Query" section. This affords a better appreciation of the scope and variety of query operations than you might obtain otherwise by sifting an alphabetical list of class members. This is not a definitive list or description of EntityManager features. Only the API documentation for EntityManager is authoritative in that respect. The point of this topic is to orient you to the possibilities. To that end, most of the public members of the EntityManager class are mentioned and receive a sentence or two of explanation. Note: the terms "manager" and "EntityManager" are interchangeable here as they are almost everywhere in DevForce documentation. Configure The EntityManager members with which to configure a new EntityManager and learn about its current configuration. Constructors have been omitted. A few members you can set or attach to: Member Summary AuthorizedThreadId Get and set the id of the thread on which this manager can run EntityManagerCreated Static event raised whenever a new EntityManager is created. Useful for tracking and uniformly configuring the managers created in multiple EntityManager scenarios EntityServerError Event raised when the EntityServer returns an exception for any reason Tag Get and set an arbitrary object. Useful for distinguishing one manager from another in multiple EntityManager scenarios UseAsyncNavigation Get and set whether property navigations that must retrieve related entities from the server will "lazy load" asynchronously. It is always true for a Silverlight EntityManager because all server requests must be asynchronous; you cannot set this value in Silverlight. It is false by default in full .NET environments where "lazy loads" tend to be performed synchronously but you can change it to true. VerifierEngine Get and set the DevForce validation engine (the validation-discover) assigned to this manager. This is the validation engine used by automatic property validation within a cached entity. Most of the configuration members you can only read: Member Summary CompositionContext Gets the CompositionContext instance that helps determine how MEF will compose some of the components that support this manager, a key part of the DevForce extensibilty story. You establish the manager's CompositionContext when you construct it. DataSourceExtension Get the string that identifies the data source targeted by this manager. You set the manager's DataSourceExtension when you construct it. DataSourceResolver Get a wealth of information about the data sources used by this manager. That environment is named in the manager's DataSourceExtension. EntityServiceOption The enumeration indicating whether the manager connects to a remote service (n-Tier), a local service (2-tier) or can connect to both a local and remote service. You establish the manager's EntityServiceOption when you construct it. IsClient Whether the manager is running on a client or on the server. The valus is usually true - meaning the manager is running on the client - but a number of processes on the server are provided with a server-side EntityManager; the EntityServer interceptor classes and your custom remote service methods are good examples. The value would be false for these server-side EntityManagers. IsEntityType Get if the supplied type is known to be an entity type. MetadataStore Get the application-wide EntityMetadataStore, the store of metadata about all currently known entity types in the application. Options Get the EntityManagerOptions, a miscellaneous bag. UsesDistributedEntityService Get whether this EntityManager communicates with a remote server as it must for a Silverlight EntityManager. It is false for 2-tier deployments. Query The EntityManager members related to querying entities, either from remote data sources or from its entity cache. Many of the members that can fetch entities from the server come in both synchronous and asynchronous versions. The asynchronous versions can be used on any client EntityManager. The synchronous versions are only available for regular .NET clients and also can be used by custom server methods calling into a server-side EntityManager. You cannot use (or even see) these synchronous methods on a Silverlight EntityManager. Members that read exclusively from the entity cache are always synchronous and are available to all EntityManagers, including Silverlight EntityManagers. Member Summary ExecuteQuery Synchronously executes its IEntityQuery query argument, returning an untyped IEnumerable of entities. Not available in Silverlight. ExecuteQuery<T> Synchronously executes its strongly typed IEntityQuery<T> query argument, returning an IEnumerable<T> of T-type entities. Not available in Silverlight. ExecuteQueryAsync Executes asynchronously its IEntityQuery query argument. An untyped IEnumerable of entities is returned in the completed event args. ExecuteQueryAsync<T> Executes asynchronously its strongly typed IEntityQuery<T> query argument. , An IEnumerable<T> of "T" entities is returned in the completed event args. ExecuteQueryForObject Synchronously executes its IEntityQuery query argument, returning a single, untyped object such as a count or the first entity that satisfies the query criteria. Not available in Silverlight. ExecuteQueryForObjectAsync<T> Executes asynchronously its strongly-typed IEntityQuery<T> query argument. A a single, untyped object, such as an integer count or the first entity that satisfies the query criteria, is returned in the completed event args. FindEntities<T> Searches the entity cache for entities of type "T" which also have an entitystate that matches one of the flag values in the EntityState argument. FindEntities Searches the entity cache for all entities whose entitystate matches one of the flag values the EntityState argument. Can optionally limit the search to entities of a specified type. FindEntity Searches the entity cache for an entity with a given EntityKey. By default the search would not return a deleted entity with that key but you can indicate with a boolean flag that you want the entity if it is deleted. FindEntityGraph Searches the entity cache for the entity "roots" passed as arguments to the method and also returns the entities related to those root entities. The "entity graph" is the combination of a root and its related entities. The extent of the graph is defined by the relationships identified in the EntitySpan argument. GetNullEntity<T> Get the "null entity" (aka, the "nullo") for the entity type "T" that represents the "entity not found". Reference navigation from an entity-in-cache (e.g., anOrder.Customer) returns the "nullo" rather than null if the entity can't be found. GetNullEntity The untyped variant of GetNullEntity<T>. GetQuery<T> Returns an EntityQuery<T> that, if executed, would query for all entities of type "T". The statement, manager.GetEntity<Customer>()), returns a query that, if executed, would retrieve all customers in the database. It is typically the foundation query to which the developer adds filter clauses to narrow the search. RefetchEntities Refreshes the entities identified in the method arguments with the most recent data available from the database. A synchronous method that is not available in Silverlight. RefetchEntitiesAsync The asynchronous version of RefetchEntities that is available in Silverlight. Some EntityManager members notify subscribers about query activity. Member Summary Querying A cancellable event raised when the manager is about to process a query. The manager has not yet determined if it will satisfy the query from its entity cache or will have to go to the server for entities. It is raised before Fetching. OnQuerying The virtual method that a derived EntityManager can override to raise the Querying event. Fetching A cancellable event raised when the manager, while processing a query, has determined that it must go to the server for entities. It is raised before sending a query to the server. It won't be raised if the manager expects to satisfy the query entirely from its entity cache. Compare to Querying. OnFetching The virtual method that a derived EntityManager can override to raise the Fetching event. Queried An event raised by the manager after it has processed a query request successfully; not raised if the query failed. The query may or may not have fetched data from the server. If it did, the event is raised after entities were retrieved successfully and merged into the entity cache. The EventArgs can tell you whether the query involved a trip to the server and, if so, which of the entities retrieved from the server (including related entities) resulted in changes to the entity cache. OnQueried The virtual method that a derived EntityManager can override to raise the Queried event. Finally, a few query-control members. Member Summary CancelAsync Cancel a pending asynchronous query. Actually not just for canceling queries. It can cancel any unfinished asynchronous operation but it is most often used to cancel queries. DefaultQueryStrategy Get and set the QueryStrategy the manager should use if the query itself does not specify a QueryStrategy. QueryCache Get the manager's QueryCache, the special cache that remembers previously issued queries. The manager may decide to fulfill a new query request entirely from its entity cache if the manager finds the query in its QueryCache. Adding and removing queries from the QueryCache is largely automatic but you can manipulate it directly once you gain access to it through this property. Save The EntityManager members related to saving cached entities with pending changes Member Summary HasChanges Get whether the manager's entity cache contains a changed entity. More specifically whether the entitystate of any cached entity is other than Unchanged. SaveChanges Save synchronously all changed entities in cache. Overloads accept optional parameters such as callback delegate, SaveOptions, and a caller- defined "user-state" object to keep track of and differentiate individual save calls. The returned SaveResult provides information about success or failure and the entities that were saved. Because synchronous, it is not available in Silverlight. SaveChanges (entities, ...) A SaveChanges variant that saves just the entities specified in the argument SaveChangesAsync Save asynchronously all changed entities in cache. Same overloads as SaveChange. Because asynchronous, the completed event args provide information about success or failure and the entities that were saved. This method is available on all .NET clients, including Silverlight. SaveChangesAsync (entities, ...) A SaveChangesAsync variant that saves just the entities specified in the argument Some EntityManager members notify subscribers about save activity. Member Summary Saving A cancellable event raised when the manager is about to save. The manager provides the handler with a list of entities it intends to save. OnSaving The virtual method that a derived EntityManager can override to raise the Saving event. Saved A cancellable event raised when the manager has saved successfully; not raised if the save failed. The event args provide information about success or failure and the entities that were saved. OnSaved The virtual method that a derived EntityManager can override to raise the Saved event. Finally, a few save-control members. Member Summary DefaultSaveOptions Get and set the SaveOptions that regulate the save unless the SaveChanges caller submits substitute SaveOptions. ForceIdFixup Immediately replace the temporary ids of newly-created entities with server-generated permanent ids for entities whose keys are generated by a custom method. The save process performs this "fix-up" automatically but you can force it to happen sooner. Because synchronous, it is not available in Silverlight. ForceIdFixupAsync The asynchronous version of ForceIdFixup available on all .NET clients including Silverlight. Access the entity cache Some EntityManager members yield insights into the manager's entity cache. Member Summary IsEntityLoaded(key) Whether an entity with given EntityKey is in the manager's cache. CacheStateManager Get the manager's CacheStateManager through which you can save and restore a copy of the entity cache in the form of an EntityCacheState. GetEntityGroup,T> Get the EntityGroup inside the cache that holds the cached instances of type "T". GetEntityGroup The non-generic version of GetEntityGroup<T> takes the entity type as a parameter. GetEntityGroups Get an array of all EntityGroups in the cache. There will be a group for every type the cache has ever seen. Many operations move entities into and out of a manager's entity cache automatically. You can manipulate this cache directly using these EntityManager members. Member Summary AddEntities Add entities to the cache as if they were new, Added entities that do not exist in the database and will be inserted if saved. AddEntity Add an entity to the cache as if it were a new, Added entity that does not exist in the database and will be inserted if saved. AttachEntities Attach entities to the cache as if they were pre-existing, Unchanged entities that might have been queried from the database. AttachEntity Attach an entity to the cache as if it were a pre-existing, Unchanged entity that might have been queried from the database. Clear Clear the cache, emptying it of all entities. Also clears the QueryCache described in the Query section above. Cleared Event raised when Clear is called. RemoveEntities (entityState, ...) Remove all entities with the given entitystate, optionally clearing the QueryCache (the default) RemoveEntities (entities, ...) Remove the entities in the argument, optionally clearing the QueryCache (the default). RemoveEntities (entityType, entityState) Remove entities of a particular and entitystate, optionally clearing the QueryCache (the default). RemoveEntity Remove the entity, optionally clearing the QueryCache (the default). ImportEntities Import shallow copies of the entities into the cache, merging them according to the provided MergeStrategy. Create, modify and delete These methods assist the developer in the course of creating, modifying and deleting entities. Member Summary AcceptChanges Treat all entities with pending changes as if they had been saved. They should appear as if they had been freshly retrieved from the database. Added and Modified entities become Unchanged, their current version values overwrite their original version values. Deleted entities are removed from cache, becoming Detached. RejectChanges Rollback all entities with pending changes. They should appear as if they had been freshly retrieved from the database. Deleted and Modified entities become Unchanged, their current version values restored from their original version values. Added entities are removed from cache, becoming Detached. CreateEntity<T> Create new entity of type "T"; the entity remains detached although the entity retains the inaccessible memory of the manager that created it. CreateEntity Create an entity dynamically with this non-geneneric version of CreateEntity. EntityChanging Cancellable event raised when a cached entity is about to change, e.g., an entity is about to be added, modified, attached, queried, imported, deleted, detached, committed or rolled back. EntityChanged Event raised when a cached entity has changed, e.g., an entity was added, modified, attached, queried, imported, deleted, deEtached, committed or rolled back. GenerateId Generate a temporary id for a newly created entity that requires custom Id generation; that temp id becomes permanent during save as result of an id fix-up process. Secure We devote a full topic to security elsewhere. In brief, all DevForce EntityManager instances must have acquired a local security context before performing most server operations. The manager may acquire that context implicitly (as when relying upon cookie-based security) or explicitly with some of these EntityManager methods. Member Summary IsLoggedIn Get whether this manager is currently authenticated. LinkForAuthentication Link this manager's security context to that of the source EntityManager passed as a parameter. Enables multiple EntityManagers to share the security context of a single master EntityManager that can login and logout on their behalf. Login Login (authenticate) synchronously with the server using the supplied credentials. Not available in Silverlight because synchronous. LoginAsync The asynchronous variant of Login, available on all client technologies. Logout Logout (de-authenticate) synchronously with the server. The server will not accept new requests from this manager until it logs-in again. Not available in Silverlight because synchronous. LogoutAsync The asynchronous variant of Logout, available on all client technologies. Principal Get the IPrincipal instance that identifies the current user and his/her roles. The concrete instance is often a richer, custom IPrincipal implementation such as a derivative of the DevForce UserBase class. Miscellaneous server calls The EntityManager can be the applications sole channel for all communications with the server. Most communications concern data. Security commands were covered above. Other kinds of server communications are addressed by the following members. Connectivity By default, the EntityManager automatically and immediately tries to connect to the server upon construction. But you may have to connect and disconnect for a variety of reasons with a method listed here: Member Summary Connect Connect to the server explicitly and synchronously. Not available in Silverlight because synchronous. ConnectAsync Connect to the server explicitly asynchronously. Available on all client platforms. Disconnect Disconnect the manager deliberately and continue running offline. IsConnected Get whether this manager believes it is connected to the server. Remote Service Method The client may call a custom application method on the application server using one of these two Invoke... methods. These methods are untyped, affording the developer the ultimate luxury of sending almost anything as parameters and receiving almost any kind of result. The developer should consider wrapping these commands in a type-safe manner before exposing them to higher level application layers. Member Summary InvokeServerMethod Call a remote server method synchronously, passing in optional service method parameters of any type. The parameters must be both serializable and understood by the server method. InvokeServerMethod returns the object result that was itself returned by the remote server method. The client may cast the result or otherwise interpret it as best it can. Not available in Silverlight because synchronous. InvokeServerMethodAsync The asynchronous variant of InvokeServerMethod, available on all .NET clients. Because it is asynchronous, the value returned by the remote service method becomes accessible on the client in the callback's event args. Push notification Two asynchronous members are involved in the server notification service (aka, Push). Member Summary RegisterCallback Register a callback method with notification service on the server, include a userToken and pass in optional service parameters. The callback gets called with each notification until it is cancelled. CancelCallback Unsubscribe to the notification service by cancelling the callback via its associated userToken
Create an EntityManager Last modified on April 20, 2011 10:29 Construct a bare EntityManager Call "new" to construct a new instance of the EntityManager class. It can be as simple as C# manager = new EntityManager(); VB manager = New EntityManager() That manager is ready to use with any entity class model available. This default constructor assumes that you want to contact the server as soon as possible and starts connecting immediately (asynchronously in Silverlight). You won't be able to do anything with the manager until you've established a security context for the EntityManager. That isn't readily apparent out-of-the-box because DevForce grants all users full access rights; you can use the newly created EntityManager to query and save without doing anything. You are sure to impose authentication constraints of some sort and when you do, the EntityManager will refuse to send any messages to the server until those constraints are met. Please establish your security plan early in the development process. Construct the EntityManager for your entity model The entity model you created using the Entity Data Model Designer also generated a custom EntityManager for you, one that derives from the DevForce EntityManager class and is enriched with additional members that make it easier to work with your entity model. Peek inside the generated code file (e.g., NorthwindIBEntitiesIB.Designer.cs). Notice the custom EntityManager near the top of the file. It might looks a bit like this: C# IbEm.DataSourceKeyName(@"NorthwindEntities")] public partial class NorthwindEntities : IbEm.EntityManager { // .. } VB <IbEm.DataSourceKeyName("NorthwindEntities")> Partial Public Class NorthwindEntities Inherits IbEm.EntityManager ' .. End Class You can construct one of these as you did the bare DevForce EntityManager. C# manager = new NorthwindEntities(); VB manager = New NorthwindEntities() Look further in the generated code to find several constructors that enable you to determine initial characteristics of the EntityManager. The first is the constructor you called above; all of its parameters are optional. C# public NorthwindManager( bool shouldConnect=true, // Whether to start connecting to the server immediately string dataSourceExtension=null, // optional target environment IbEm.EntityServiceOption entityServiceOption=IbEm.EntityServiceOption.UseDefaultService, string compositionContextName=null) : base(shouldConnect, dataSourceExtension, entityServiceOption, compositionContextName) {} VB Public Sub New(Optional ByVal _ shouldConnect As Boolean =True, _ Optional ByVal dataSourceExtension As String =Nothing, _ Optional ByVal entityServiceOption As IbEm.EntityServiceOption =IbEm.EntityServiceOption.UseDefaultService, _ Optional ByVal compositionContextName As String =Nothing)
MyBase.New(shouldConnect, dataSourceExtension, entityServiceOption, compositionContextName) End Sub Another generated constructor gathers all options together into an EntityManagerContext. The EntityManagerContext allows for additional settings not available in the other constructors, including options to control refetch behavior and connect to a non-default application server. C# public NorthwindManager( IbEm.EntityManagerContext entityManagerContext) : base(entityManagerContext) {} VB Public Sub New(ByVal entityManagerContext As IbEm.EntityManagerContext) MyBase.New(entityManagerContext) End Sub These constructors are available for the base EntityManager class too. You can read the DevForce API documentation for details about these parameters and the other constructors. You might consider a brief digression now to learn about creating a disconnected EntityManager and creating EntityManagers that target different databases under different execution scenarios. Custom EntityQuery properties The custom EntityQuery properties are one reason you may prefer to create a new custom EntityManager such as NorthwindEntities rather than a bare-bones EntityManager. Peeking again at the generated code for NorthwindEntities we find query properties generated for each of the entity types. Here is one for querying Customers. C# public IbEm.EntityQuery<Customer> Customers { get { return new IbEm.EntityQuery<Customer>("Customers", this); } // IbEm == IdeaBlade.EntityModel } VB Public ReadOnly Property Customers() As IbEm.EntityQuery(Of Customer) Get ' IbEm == IdeaBlade.EntityModel Return New IbEm.EntityQuery(Of Customer)("Customers", Me) End Get End Property It's a nice touch - a bit of syntactic sugar - that can make a LINQ query a little easier for developers to read and write: C# query1 = northwindManager.Customer.Where(...); query2 = bareManager.GetQuery<Customer>().Where(...); VB query1 = northwindManager.Customer.Where(...) query2 = bareManager.GetQuery(Of Customer)().Where(...) The two queries are functionally the same. Most developers prefer query1.
The EntityManager is not thread-safe The EntityManager is not thread-safe and neither are entities. An EntityManager throws an exception if you try to use it on multiple threads. This topic touches upon the challenges and risks of cross-threading and describes how you use the AuthorizedThreadId to control which thread an EntityManager calls home.
Thread mischief Question: Why did the multithreaded chicken cross the road? Answer: other to side. To the get. IdeaBlade used to receive desperate support calls about applications that used to work but have suddenly begun behaving erratically under real user loads. The application would occasionally return seemingly impossible results. An entity might say it was unmodifed when it actually had changes. Entities would mysteriously disappear from lists or appear in lists where they should not. Such errors didn't show up in testing. They appeared in production or during stress testing ... not predictably but occasionally. "Are you using the EntityManager in multiple threads?" we would ask? "No" was the usual reply. But, upon further investigation, sure enough, someone had referenced the EntityManager - or one of its entities - on another thread. Today the EntityManager throws an exception if you call one of its members on more than one thread. You get news of your mistake early, during development, while you are calm and have time to fix it. The EntityManager is not thread-safe The EntityManager is not thread-safe. The RIA Services DomainContext, the Entity Framework ObjectContext, and the NHibernate Session classes aren't thread-safe either. Internally the EntityManager maintains mutable collections of mutable objects. That is in the very nature of entities and of the components with which you manage them. They cannot be made thread safe. You may think you need to write background tasks to improve performance. You generally do not. The EntityManager can perform many operations asynchronously for you. Perhaps you want to retrieve several large entity collections in background. You can launch multiple asynchronous queries from a single EntityManager running on the main thread; DevForce will handle the background threading and marshal the results back to the main thread safely. See the topic on asynchronous queries. AuthorizedThreadId and the EntityManager's home thread An EntityManager remembers the id of the thread on which it was created. This identifies its home thread. The EntityManager's AuthorizedThreadId property tells you what thread id that is. You won't care about this id as long as you stay clear of multi-threaded scenarios. Unfortunately, some times you can't. Two scenarios come to mind: 1. Automated MS Tests of asynchronous queries 2. ASP.NET clients that maintain an EntityManager across requests. In both cases, the EntityManager can be called on a thread other than the thread on which it was created. That is usually a big, red "danger" flag. It turns out to be safe in these "free threading" scenarios because the EntityManager will never be called on its original thread again. Of course the EntityManager doesn't know that. It will throw an exception (see below) when called on the new thread, because it assumes the worst and fears that concurrent multi- threaded access may occur. We have to tell it that its home thread has changed which we do by setting its AuthorizedThreadId property to the id of the new thread. Here's how to set the AuthorizedThreadId to the currently executing thread: C# manager.AuthorizedThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; VB manager.AuthorizedThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId A moment ago you read: "the EntityManager will never be called on its original thread again." We recommend that you be deeply suspicious of any such claim. You may trust ... but you should verify. Fortunately, you get that verification for free when you change the AuthorizedThreadId. If something calls upon the EntityManager back on the original thread, the EntityManager will throw an exception ... because the original thread is no longer the home thread. Let's try it. C# // Now executing on original thread with Id=18 // Change the home ThreadId to a phoney manager.AuthorizedThreadId = 1234;
// Call the EntityManager on thread Id=18 Manager.Customers.ExecuteAsync(); // Throws exception VB ' Now executing on original thread with Id=18 ' Change the home ThreadId to a phoney manager.AuthorizedThreadId = 1234
' Call the EntityManager on thread Id=18 Manager.Customers.ExecuteAsync() ' Throws exception ExecuteAsync throws an exception: Customers query failed with exception: System.InvalidOperationException: An EntityManager can only execute on a single thread. This EntityManager is authorized to execute on the thread with id=1234; the requested operation came from the thread with Id=18. ... EntityManagers on ASP Clients In most cases, an ASP.NET web application developers should create a new EntityManager for each request. However, when writing a "wizard" that carries user data forward from request to request, some developers choose to hold an EntityManager in an in-memory Session variable. That way, they can reuse cached entities across requests rather than have to struggle with managing temporary storage for changed entities. There are other, perhaps safer and more scalable ways, to address this scenario. In essence, you store the EntityManager in Session just before the current request ends; when the follow-up request begins, you pull the EntityManager instance out of Session and assign it to your manager variable. This will fail. The follow-on-request is on a different thread than the prior request. The EntityManager you put into Session is pinned to the thread of the prior request. It will throw the System.InvalidOperationException the moment you use it on the new request. The solution is as described above. Set the AuthorizedThreadId property to the new request's thread immediately after restoring the EntityManager from Session, well before calling any other of its members. Disabling thread id checking You can disable thread id checking by setting AuthorizeThreadId to null. This option exists for backward compatibility with earlier versions of DevForce. We can't think of a good reason to use it. It is especially dangerous in server methods which may be called by multiple threads. Please only disable thread id checking for a very good reason and with full awareness of the risks. Entities are not thread safe either The EntityManager throws an exception when exercised on multiple threads. Unfortunately, there is no similar guard logic for entities nor for collections of entities. You have to ensure that they are used in a thread-safe manner. As mentioned, it is best to avoid multiple threads in the first place. You rarely need background threads in a client application. Regard claims to the contrary with deep suspicion. Server programming is another matter. Strive to make custom server logic single-threaded. Avoid stateful static classes. When multiple threads are unavoidable, make sure you use the necessary synchronization logic. Avoid multi-threaded programming Multi-threaded, concurrent programming is a deep and difficult topic beyond the scope of DevForce documentation. It really is "brain surgery". Experts agree: 1. Design so you don't need multiple threaded code 2. Hire and learn from an expert unless you are an expert 3. Don't write it alone; scrutinize mult-threaded code with a partner 4. Attack that code with automated tests.
Create an offline EntityManager You may want to create an offline EntityManager during testing and development or to run the application when disconnected from the network and database. The most frequently used EntityManager constructor is declared like this: C# public EntityManager( bool shouldConnect=true, // Whether to start connecting to the server immediately string dataSourceExtension=null, IbEm.EntityServiceOption entityServiceOption=IbEm.EntityServiceOption.UseDefaultService, string compositionContextName=null); VB Public Sub New( _ Optional ByVal shouldConnect As Boolean =True, _ Optional ByVal dataSourceExtension As String =Nothing, _ Optional ByVal entityServiceOption As IbEm.EntityServiceOption =IbEm.EntityServiceOption.UseDefaultService, _ Optional ByVal compositionContextName As String =Nothing) End Sub All of the parameters are optional. The parameter of interest in this topic is shouldConnect which is true by default. That means that the manager automatically starts connecting to the EntityServer. You'll probably do just that in production. But you might want to set it false. Maybe your application doesn't have a connection to the server at right now. Maybe the user has launched the application while several miles high on an airplane. You could design the application to run disconnected until a network connection can physically be established. Maybe the application shouldn't connect at all during this session. Many automated tests require the presence of an EntityManager, perhaps one populated with test entities. They don't need - and don't want - to talk to the server or the database. That would only make the tests slower and vulnerable to interrupted connections and disabled databases. Many programmers run disconnected when running the application in development for the same reasons; there might not even be a database in the early stages of development. Of course you won't be able to save changes while disconnected. Fortunately, most automated tests don't need to save and the savvy developer can easily divert save requests while in development mode. You can create a disconnected EntityManager like this: C# manager = new NorthwindEntities(shouldConnect: false); // ... manager.ConnectAsync(connectCallback); // Time to connect VB manager = New NorthwindEntities(shouldConnect:= False) ' ... manager.ConnectAsync(connectCallback) ' Time to connect
Reconnect an offline EntityManager This topic covers how an EntityManager can lose its connection to the server and reconnect later.
Connected by default By default, an EntityManager automatically and immediately tries to connect to the server upon construction; ordinarily you do not have to initiate a connection explicitly. Becoming disconnected Various circumstances may cause an EntityManager to become disconnected. 1. You deliberately create an offline manager. 2. You decide to take the manager offline by calling its Disconnect method. 3. The application loses its connection to the server as it began a server operation or in the midst of a server operation. In all three cases, the EntityManager enters a disconnected state, a fact easily determined by accessing its IsConnected property. The IsConnected property reports whether the manager "believes" it is connected to the EntityServer. It may "think" it is connected when, in fact, it could not reach the server if it tried. Losing the connection The application decided to disconnect in examples #1 and #2. In the 3rd example, the application loses the connection involuntarily and without warning. The EntityManager responds by setting itself in the disconnected state and then prepares to throw an EntityServerConnectionException. You can (and should) be ready to intercept and handle that exception by attaching a handler to the manager's EntityServerError event. See the the topic devoted to handling the EntityServerError event. Your handler next decides how to proceed. Rather than shut down, you may choose to tell the rest of the application about the problem and continue running in a well-defined offline mode. Anticipating connectivity problems It is wise to listen to the .NET event that announces the gain and loss of network connectivity. The requisite event differs from one client platform to the next but there always is such an event. However, do not depend upon it to reflect the true and complete state of affairs. A flakey connection could break in the middle of an attempt to connect. The network connection may be good but the middle tier server could be down. The EntityManager will disconnect and prepare to throw an exception whatever the cause. You can glean the details from the exception in the body of the EntityServerErrorEventArgs . Your application could lose contact with the server at any time. Plan for it. Remember to handle the EntityServerError event if you want your application to survive a disconnection or if you prefer to shutdown gracefully. Restore the connection An EntityManager will not reconnect spontaneously. Once offline, it stays offline until you reconnect explicitly. A DevForce application with a well-stocked entity cache can operate offline effectively for a long time, albeit with limited functionality. Your application must decide when to try to reconnect. It may decide to try again if a .NET event signals restored network access, or a retry timer fires, or in response to a user action. Whatever the trigger, your code must call the EntityManager's Connect or ConnectAsync methods; only the async method is available in Silverlight. As always, code defensively and be prepared for connectivity failures.
Target different databases dynamically
You can target different databases dynamically by creating the EntityManager with a non- default DataSourceExtension.
The need for different data source targets When you start writing an application, you probably only have one database, your development database. That won't last long. Eventually, the application will query and save to one database during development, and to other databases in your staging, test, and production environments. That's four different database instances (all with the same schema one hopes). You need some way to switch among these databases depending upon which the environment you want to be in. You may put the "switch" in a configuration file on the client. You might determine it programmatically on the client. Either way, the client application should be able acquire the value of the switch and choose the appropriate server environment. Let's generalize this thought. Suppose that you are building a multi-tenant application with a different database for each tenant. There will be far more than four databases. You probably don't want to set up different servers for each one. It is easier to have a single (load-balanced) server address and tell the server at that address which tenant database to use. User input can provide the client application with the information it needs to determine which tenant environment to use. Now we have to tell the server about it. The EntityManager tells the server which data source to use by sending two types of information in each request: 1. The DataSourceKeyName that identifies the data source schema 2. The DataSourceExtension that identifies which one of the schema-matching databases to use. DataSourceKeyName Every entity model is associated with a data source. The data source is typically a database and we'll assume it is a database in this discussion. The schema of that database maps to the entity classes in the model. You could say that the schemas of the entity model and the database are matched. However, the entity model doesn't know which concrete database holds the actual data for the application. Your application code shouldn't know either. The actual database to use at runtime is determined by a connection string; that's a configuration concern best relegated outside the client code base. A distributed client application (e.g., a Silverlight application) should never contain database connections strings of any kind. The connection string is never needed on the client and it's a serious security violation to have one there. Instead a DevForce application refers to the database by name, by its DataSourceKeyName resolution logic on the server determines which connection string to use based on the DataSourceKeyName associated with the entity classes in the request. For example, the DataSourceKeyName could be "NorthwindEntities" as it often is in our sample code. We know that's the key because we typed that name when we created the entity model. We can also tell by inspecting the attributes on the generated EntityManager and entity class files: C# IbEm.DataSourceKeyName(@"NorthwindEntities")] public partial class NorthwindEntities : IbEm.EntityManager {}
[IbEm.DataSourceKeyName(@"NorthwindEntities")] public partial class Customer : IbEm.Entity {} VB <IbEm.DataSourceKeyName("NorthwindEntities")> Partial Public Class NorthwindEntities Inherits IbEm.EntityManager End Class
<IbEm.DataSourceKeyName("NorthwindEntities")> Partial Public Class Customer Inherits IbEm.Entity End Class DataSourceExtension The DataSourceKeyName identified the database schema to use - some kind of Northwind database - but it didn't tell us which concrete database to use. It doesn't tell us which of many possible versions of the Northwind database to use. That's the job of the DataSourceExtension. The DataSourceExtension is a string that DevForce combines with the DataSourceKeyName to determine the concrete database to use. The "NorthwindEntities" DataSourceKeyName tells the server that the client wants some version of a Northwind database; the value of the DataSourceExtension tells the server which specific Northwind database. The DataSourceExtension string value is up to you. You devise your own extension naming scheme. It might be "Dev", "Test", "Stage" and "Prod". It could be the Tenant ID. The trick is in what you do with that string Construct an EntityManager with a DataSourceExtension The client application, having acquired the appropriate DataSourceExtension string value for the logged-in user, passes that DataSourceExtension into the constructor of a new EntityManager. Here is the declaration for the most frequently used EntityManager constructor: C# public EntityManager( bool shouldConnect=true, string dataSourceExtension=null, // data source environment to use IbEm.EntityServiceOption entityServiceOption=IbEm.EntityServiceOption.UseDefaultService, string compositionContextName=null); VB Public Sub New( _ Optional ByVal shouldConnect As Boolean =True, _ Optional ByVal dataSourceExtension As String =Nothing, _ Optional ByVal entityServiceOption As IbEm.EntityServiceOption =IbEm.EntityServiceOption.UseDefaultService, _ Optional ByVal compositionContextName As String =Nothing) End Sub Our focus in this topic is on the dataSourceExtension parameter which is null by default. That means the EntityManager expects to query and save data to the default data source. The server should know which database is the default database. If the client wanted to target a particular database, say the "Test" database, it could do so with code such as this: C# manager = new NorthwindEntities(dataSourceExtension: "Test" ); VB manager = New NorthwindEntities(dataSourceExtension: "Test" ) Henceforth, this manager is dedicated to the "Test" environment and will always tell the server to query and save to the "Test" version of the database. Please be advised that currently the '+' sign is a reserved character that must not be used in a DataSourceExtension string. If you do, you will receive the following error: IdeaBlade.Core.IdeaBladeException: Unable to find a compositionContext with the name: ..... Other than this, any character is safe as long as it's valid for a file name. The server interprets the DataSourceExtension What the server actually does with the DataSourceExtension is beyond the scope of this topic. In brief, DevForce server components combine the DataSourceExtension from the EntityManager with the DataSourceKeyName inscribed in the entity types mentioned in the request. Remember that query and save requests involve entities and each entity is adorned with an attribute identifying its DataSourceKeyName - the kind of database it maps to. That combination of strings is handed to a DataSourceResolver - one you can replace - that identifies the appropriate database and its connection string. The entityserver is then equipped to process the request. Many models per manager The EntityManager can pool entities from many models with multiple data sources. The EntityManager is not limited to the entities of one model or one database. A single EntityManager instance can contain POCO entities and regular DevForce entities from multiple models simultaneously. query entities from different models. save changed entities to different data sources in a single transaction; DevForce uses a distributed transaction if the data sources support it. By default, when DevForce generates the base code for a model, it also code generates a custom EntityManager that contains pre-defined queries for entities in that model (e.g. entityManager.Customers where entityManager = new NorthwindIBEntities()). However, these pre-defined queries are just for syntactic convenience, and in fact, any EntityManager can query for entities from any model. Instead of using the pre-defined property for the query, you just call GetQuery with the entity type that you want: C# manager = new EntityManager(); // Creates an "untyped" EntityManager // - but you can use any EntityManager var query = manager.GetQuery<Customer>(); // "Get all customers" query query.ExecuteAsync(); // listen for completed event and do something VB manager = new EntityManager() ' Creates an "untyped" EntityManager ' - but you can use any EntityManager dim query = manager.GetQuery<Customer>() ' "Get all customers" query query.ExecuteAsync() ' listen for completed event and do something The entity types are tied to a particular data source by its DataSourceKey name. The EntityManager and EntityServer learn from the entities they see which data sources are involved in a requested operation. That model-specificity of entities is apparent in their generated class code; notice the DataSourceKeyName attribute adorning the Customer class: C# IbEm.DataSourceKeyName(@"NorthwindEntities")] public partial class Customer : IbEm.Entity {} VB <IbEm.DataSourceKeyName("NorthwindEntities")> Partial Public Class Customer Inherits IbEm.Entity End Class DevForce generates a strongly-typed subclass of the EntityManager such as NorthwindIBEntities that appears to be tied to a single model because it has supplemental properties and methods that are specific to one model. Managers are typically used with one model and these extra member make it convenient to work with that model. But, like every EntityManager, the derived manager can contain entities from multiple models, query from multiple models, and save to multiple databases. DevForce code generation does add a DataSourceKeyName attribute to the derived EntityManager. The attribute is notional and in no way restricts that EntityManager's ability to work simultaneously with entities from multiple models.
Multiple EntityManagers Create multiple EntityManagers when you need to isolate one set of entities (and their changes) from another set of unmodified entities. The need typically arises when launching an editor in a "sandbox"; changes within the editor are provisional and confined to the editor scope.
Some applications only need a single EntityManager instance. That instance can hold in its cache every entity the application ever needs. The user queries, changes, and saves entities using that one manager. An ASP application should only require one manager. However, we often see multiple EntityManagers in smart client and RIA applications. We tend to cache entities for longer periods - often the entire session - and soon discover that we need to maintain separate, isolated caches. Entities are unique within a manager's cache - there can be only one Customer with Id=42 in a particular cache. If I change the customer's name from "Acme" to "Beta", that change is visible to every view referencing that customer. That isn't always desirable. We may want two copies of Customer #42 that we use for different purposes. Multiple managers help us work with multiple copies of the same entity and they provide entity isolation because they each have their own caches. Multi-manager scenarios Two multi-manager scenarios are common: 1. "Sandbox editor" - when you want to isolate the edited entities from the main set of entities. You don't want the changes to propagate to the main set until they are saved. 2. "Search manager" - a manager's cache can grow quite large as the user searches for items as when browing a product catalog. Once specific items are selected, the interim search results are no longer interesting and they hog memory. It's easy and harmless to throw them away if they are confined to a search-only manager that you can clear. Sandbox editor Customer #42 can appear twice in the same application if it is cached in two separate managers. Suppose the main manager 'A' supports a customer selection view and manager 'B' is the isolated manager within the "sandbox editor". Changing the customer's name in the editor from "Acme" to "Beta" effects the Customer #42 in manager 'B' and is visible only within the editor. Users looking at the customer selection view will see the Customer #42 in manager 'A' whose name remains "Acme". If the user saves the name change in the editor, the stored name of Customer #42 becomes "Beta". It's still "Acme" back in manager 'A' and in the customer selection view. The situation for managers 'A' and 'B' is analagous to two separate users who are looking at the same customers. If the user should see a refreshed and current view of Customer #42, the developer must take steps to update that customer in manager 'A', perhaps through a messaging mechanism. See the blog post "Sandbox" Editors with ClientUI Contacts for an example and discussion. Take this a step further. Suppose the application can edit multiple customers at the same time. It might open an editor for Customer #42 and another for Customer #007. Now there are three managers in play, each with their isolated caches. The user can toggle among them, making changes to #42, then #007, then back to #42, then canceling the #007 edit session, and finally saving changes to #42 which propagate back to the main customer selection view. Multiple managers are handy when you have to juggle ongoing tasks like this. Search manager Imagine a shopping application in which manager 'A' holds products to purchase. The user, while searching for more items to purchase, is busily querying products, perhaps hundreds of them, most of which will not end up in the shopping cart. The volume could be substantial and the toll on local memory severe. When the user is ready to checkout, those unwanted products are dead weight. You could try to identify and purge them from manager 'A', taking care to separate the product entities mentioned in the cart from the vast majority of unwanted products. It might be easier to conduct the search using a manager 'B'. When the user picks an item, you copy it from 'B' to 'A' (using ImportEntities ). When the shopping is over, you clear manager 'B'. Create a second EntityManager Create a second manager the same way you did the first. You have the same range of options. Consider using the handy copy constructor that creates a new EntityManager configured like its source. C# var secondManager = new EntityManager(firstManager); VB Dim SecondManager = New EntityManager(FirstManager) One critical consideration: each new EntityManager gets its own security context by default .. and must be separately logged in by default. You may not notice this during early development, before you have established your authentication process. You will discover it if you require an EntityManager to login. The second manager will throw an exception when you try to use it, complaining that the manager has not been logged in (see Secure). Rarely (if ever) do you want to authenticate each manager separately, not even if login is implicit and seemingly automatic. Fortunately, you don't have to. You can tell the second manager to get its security context from the first manager using the LinkForAuthentication method. C# var em1 = new EntityManager(); em1.Login(new LoginCredential("demo", "demo", "earth")); var em2 = new EntityManager(); em2.LinkForAuthentication(em1); VB Dim em1 = New EntityManager() em1.Login(New LoginCredential("demo", "demo", "earth")) Dim em2 = New EntityManager() em2.LinkForAuthentication(em1) The new EntityManager, em2, has the same security contexts as its prototype, em1. It will not require its own login to fetch or save data. Don't call LinkForAuthentication if you used the copy constructor described above; it establishes that linkage automatically.
The entity cache The entity cache is an in-memory container of entity objects controlled by its EntityManager . You query entities into the cache. You display and edit entities in cache. You save changed entities from the cache to the database. This topic tells you what you need to know about the cache before encountering it again in other topics.
The EntityManager maintains a cache of entities. A query puts retrieved entities in the cache. You add newly created entities to cache. When you ask the manager to save, it saves the changed entities that it finds in its cache. Most of the time you work with entities that reside in a manager's entity cache. You can work with entities that are not yet in cache and entities that were once in cache but have since been detached. But that's unusual. Many of the important features of entities, including the ability to navigate to related entities and to save changes, are only available when the entities reside in cache. Entities in cache are unique An entity is identified by its EntityKey, an object consisting of a type (e.g., Customer) and the values of its key properties (e.g., the value returned by the CustomerID property). All entities in cache have unique EntityKeys; a cache can't have two instances of Customer with the same CustomerID. Of course no one can prevent you from creating two Customer objects with the same CustomerID. But they can't both be in the same EntityManager cache at the same time. This is true for deleted entities as well. They may seem invisible but they are there, in cache, until they are saved. Only after they have been saved successfully do they depart the cache. Add entities to cache Entities usually enter the cache as a by-product of a query. DevForce always puts queried entities in cache. You add entities to cache explictly in three ways: 1. By adding it to a manager with the AddEntity method. An added entity is regarded as a new entity that will be inserted into the database if saved. 2. By attaching it to a manager with the AttachEntity method. An attached entity is regarded as an existing, unmodified entity like one that has been queried. 3. By importing it with the manager's ImportEntities method. Remove entities from cache Deleting an existing entity doesn't remove it from cache but saving a deleted entity does. The save removes the deleted entity implicitly. You can remove an entity from cache explicitly as well: C# manager.RemoveEntity(someCustomer); VB manager.RemoveEntity(someCustomer) Call the manager's Clear method to remove every entity from the cache. Is the entity in cache or not? In the code above, someCustomer is a reference to a Customer entity. They entity didn't disappear because we removed it from cache. It's still an entity. It's now a Detached entity. We can ask the entity if it is in cache or Detached by inquiring about its entitystate. The following line verifies that someCustomer was detached after we removed it. C# Assert.IsTrue(someCustomer.EntityAspect.EntityState.IsDetached()); VB Assert.IsTrue(someCustomer.EntityAspect.EntityState.IsDetached()) EntityState can tell us more than whether the entity is attached or detached.
Find entities in cache You can query the cache for entities and confine that query to the cache with a CacheOnly QueryStrategy . But there's a more direct way to find entities in the cache: use one of the FindEntities methods such as this one that finds an entity by its EntityKey: C# var foundCust = manager.FindEntity(cust1.EntityAspect.EntityKey); Assert.AreSame(cust1, foundCust); VB Dim foundCust = manager.FindEntity(cust1.EntityAspect.EntityKey) Assert.AreSame(cust1, foundCust) Unlike a query, you can find an entity in cache even if it is deleted. C# cust1.EntityAspect.Delete(); // marked for deletion var foundCust = manager.FindEntity(cust1.EntityAspect.EntityKey, includeDeleted:true ); Assert.AreSame(cust1, foundCust); VB cust1.EntityAspect.Delete() ' marked for deletion Dim foundCust = manager.FindEntity(cust1.EntityAspect.EntityKey, includeDeleted:=True ) Assert.AreSame(cust1, foundCust) You can also find entities in cache using LINQ for objects: C# var foundCust = manager .FindEntities(EntityState.AllButDetached) .OfType<Customer>() .Where(c => c.CompanyName == "Acme") // cust1's name .FirstOrDefault();
Assert.AreSame(cust1, foundCust) Notice that FindEntities filters by entitystate; here we ask for all cached states (AllButDetached). FindEntities returns an IEnumerable; we cast it to IEnumerable of Customer in order to query it with LINQ. Export cache as an EntityCacheState The EntityManager itself is not serializable. But its cache contents are serializable when in the form of an EntityCacheState . An EntityCacheState is a snapshot of the entities in cache. This snapshot can be handed around inside the client application, serialized to file, restored from file, even sent to the server as a parameter in a remote server method call. To get an EntityCacheState, start with the manager's CacheStateManager . Its GetCacheState method can return an EntityCacheState with all or only some of the entities in cache. In the following unrealistic example, we use an EntityCacheState to copy a cache from one manager to another: C# // Get EntityCacheState with all cached entities using the CacheStateManager var ecs = manager1.CacheStateManager.GetCacheState();
// Create 2nd manager var manager2 = new EntityManager(shouldConnect: false);
// "Restore" into manager2 with the contents of the ECS from manager1 manager2.CacheStateManager.RestoreCacheState(ecs);
// Prove that manager2 has a customer with same ID as cust1 var foundCust = manager2.FindEntity(cust1.EntityAspect.EntityKey);
// But the foundCust is not the same as cust1 // because cust1 still belongs to manager1 Assert.AreNotSame(cust1, foundCust); VB ' Get EntityCacheState with all cached entities using the CacheStateManager Dim ecs = manager1.CacheStateManager.GetCacheState()
' Create 2nd manager Dim manager2 = New EntityManager(shouldConnect:= False)
' "Restore" into manager2 with the contents of the ECS from manager1 manager2.CacheStateManager.RestoreCacheState(ecs)
' Prove that manager2 has a customer with same ID as cust1 Dim foundCust = manager2.FindEntity(cust1.EntityAspect.EntityKey)
' But the foundCust is not the same as cust1 ' because cust1 still belongs to manager1 Assert.AreNotSame(cust1, foundCust) A CacheStateManager can also save or restore an EntityCacheState from a file. Entity cache structure You rarely need to probe around inside the entity cache itself. The cache-only query and the Find methods are the preferred ways to retrieve entities from cache. When you need to watch the cache for activity regarding a particular type, it helps to know about EntityGroups . The cache is organized a collection of EntityGroups. Each group holds the cached entities for a particular type of entity. You can discover what entity types the manager has seen by asking for its EntityGroups C# manager = new EntityManager(shouldConnect:false); Assert.AreEqual(0, manager.GetEntityGroups().Count()); VB manager = New EntityManager(shouldConnect:=False) Assert.AreEqual(0, manager.GetEntityGroups().Count()) A new manager's cache has no groups. It acquires groups as different entity types are added to the cache or are referred to by an entity that was queried into cache. C# manager = new EntityManager(shouldConnect:false); manager.AddEntity(new Customer()); Assert.AreEqual(1, manager.GetEntityGroups().Count()); VB manager = New EntityManager(shouldConnect:=False) manager.AddEntity(New Customer()) Assert.AreEqual(1, manager.GetEntityGroups().Count()) You can ask for a specific EntityGroup: C# customerGroup = manager.GetEntityGroup<Customer>(); VB customerGroup = manager.GetEntityGroup(Of Customer)() Clearing the manager's cache (manager.Clear()) removes all groups. Listen to cache changes The cache can tell you when an entity has been attached, changed, or detached. The following fragment shows how to listen for any change to the cache or to cache changes for a particular entity type. C# // Listen for all changes to cache var cacheChanges = new List<EntityAction>(); int cacheChangeCount = 0; manager.EntityChanged += (s, e) => cacheChanges.Add(e.Action);
// Listen for changes to customers in cache var custChanges = new List<EntityAction>(); int custChangeCount = 0; var custGrp = manager.GetEntityGroup<Customer>(); custGrp.EntityChanged += (s, e) => custChanges.Add(e.Action);
// Employee change var emp = new Employee {EmployeeID = 42}; manager.AttachEntity(emp); cacheChangeCount++;
Assert.AreEqual(cacheChangeCount, cacheChanges.Count(), "not all cache changes were signaled");
Assert.AreEqual(custChangeCount, custChanges.Count(), "not all cust changes were signaled");
Assert.IsTrue(cacheChangeCount > custChangeCount, "should have more cache changes than cust changes"); VB ' Listen for all changes to cache Dim cacheChanges = New List(Of EntityAction)() Dim cacheChangeCount As Integer = 0 AddHandler manager.EntityChanged, Sub(s, e) cacheChanges.Add(e.Action)
' Listen for changes to customers in cache Dim custChanges = New List(Of EntityAction)() Dim custChangeCount As Integer = 0 Dim custGrp = manager.GetEntityGroup(Of Customer)() AddHandler custGrp.EntityChanged, Sub(s, e) custChanges.Add(e.Action)
' Customer changes Dim cust = New Customer() manager.AddEntity(cust) cacheChangeCount += 1 custChangeCount += 1
' Employee change Dim emp = New Employee With {.EmployeeID = 42} manager.AttachEntity(emp) cacheChangeCount += 1
Assert.AreEqual(cacheChangeCount, cacheChanges.Count(), "not all cache changes were signaled")
Assert.AreEqual(custChangeCount, custChanges.Count(), "not all cust changes were signaled")
Program asynchronously All client / server communications in Silverlight must be asynchronous and desktop applications probably should be. Asynchronous programming requires some care and techniques that may be unfamiliar. This topic describes the asynchronous operations in DevForce and how to use them.
The DevForce EntityManager in the full .NET library sports numerous methods for communicating from the client to the server, split 50/50 between synchronous and asynchronous versions. DevForce synchronous operations When using the synchronous versions, the client code makes a call and waits for the server to return. C# results = manager.ExecuteQuery(someQuery); doSomething(results); // called after server returns VB results = manager.ExecuteQuery(someQuery)() doSomething(results) ' called after server returns The application on the calling thread blocks until the server returns its reply. If it takes awhile, the calling code waits. If the calling code is UI code, the user will be staring at a frozen screen until the queried entities arrive. It could be a long wait. The asynchronous operations might be a better choice. They don't deliver server results any faster. They do let you keep the UI alive. The user might be able to do other work while waiting. Even if they can't, the application can show that it is still running and perhaps tell the user how matters are progressing. DevForce asynchronous operations Many synchronous methods aren't even available in the Silverlight EntityManager, since Silverlight disallows synchronous communications with the server. Only queries which may be fulfilled from cache can be executed synchronously in Silverlight. All service methods are asynchronous; they include: ConnectAsync LoginAsync LogoutAsync ExecuteQueryAsync ExecuteQueryAsync<T> SaveChangesAsync RefetchEntitiesAsync InvokeServerMethodAsync You have to take a different approach when using these methods. The synchronous code we wrote earlier doesn't translate literally to an asynchronous alternative. For example, this won't do what you're expecting: C# // Not what you are expecting results = manager.ExecuteQueryAsync(someQuery); doSomething(results); // called immediately, before results arrive VB ' Not what you are expecting results = manager.ExecuteQueryAsync(someQuery)() doSomething(results) ' called immediately, before results arrive Two critical observations: The results variable is not an entity collection. Asynchronous methods don't return results. They return an operation coordinator object, as we'll see. That "operation coordinator" object will contain queried entities at some point in the future. But query results won't be available when doSomething(results) is called. More precisely, they won't be available if query processing includes a trip to the server. If the query can be satisfied entirely from cache, the results are returned immediately and the operation appears to be synchronous. We ignore that possibility for now as this discussion concerns operations that talk to the server. It is in the nature of any asynchronous method call that control flows immediately to the next line and you cannot predict when the server will return with results. If you care when the results arrive, you must provide some means for the asynchronous operation to notify you. DevForce asynchronous methods can notify you in one of two ways: 1. by invoking your callback method 2. by raising a Completed event C# // with callback manager.ExecuteQueryAsync(query1, queryCompletedCallback); // with CompletedEvent operation = manager.ExecuteQueryAsync(query2); operation.Completed += queryCompletedHandler; VB ' with callback manager.ExecuteQueryAsync(query1, queryCompletedCallback) ' with CompletedEvent operation = manager.ExecuteQueryAsync(query2) AddHandler operation.Completed, AddressOf queryCompletedHandler All DevForce asynchronous methods support these two approaches. Anatomy of an asynchronous method The ExecuteQueryAsync signature tells a more complete story: C# public EntityQueryOperation<T> ExecuteQueryAsync<T>( IEntityQuery<T> query, Action<EntityQueryOperation<T>> userCallback = null, Object userState = null) VB Public Function ExecuteQueryAsync(Of T)( ByVal query As IEntityQuery(Of T), _ ByVal Optional userCallback As Action(Of EntityQueryOperation(Of T)) = Nothing, _ ByVal Optional userState As Object = Nothing _ As EntityQueryOperation(Of T) Let's break this signature down starting with the method's returned value and proceeding through the parameters. All asynchronous methods return an "operation coordinator" object ending in the word "Operation". In this example, it is an EntityQueryOperation with a type parameter 'T' specifying the type of the query result. 'T' would be Customer if the query returned Customer entities. The other asynchronous methods have their own operation object types such as the EntitySaveOperation for a save. They all derive from the DevForce BaseOperation class and therefore share many of the same behaviors and members such as a Completed event. The operation object is not the operation itself. It is an object that represents the operation in progress. You can ask it questions such as "are you finished?" and "do you have errors?". You can tell it to cancel the operation. It is a vehicle for coordinating the caller and the operation. Most developers just call it "operation" for the sake of brevity. Most asynchronous methods take a functional parameter such as the query. This is the same parameter that would be passed into the corresponding synchronous method.
Next is the optional callback method. The callback is an Action that takes the operation coordination object as its sole parameter. This is the same object that was returned by the method, the EntityQueryOperation<T> object in our example. The last parameter is an optional userState. The userState is an arbitrary object of your choosing. You supply a userState when you need to pass some information to the callback method or Completed event handler. You might use it to distinguish one query from another. The userState object can be as simple as an integer or it can be an arbitrarily complex custom type. DevForce assigns a Guid if you don't specify a value. The userState object need not be serializable because it never leaves the client; the server never sees it. Callback methods An asynchronous operation callback method takes an operation coordination object parameter as seen in this example. C# void AllCustomersQuery() { var query = new EntityQuery<Customer>(); manager.ExecuteQueryAsync(query, CustomerQueryCallback); }
void CustomerQueryCallback(EntityQueryOperation<Customer> op) { if (op.CompletedSuccessfully) { var resultList = op.Results; Console.WriteLine("Query returned {0} entities", resultList.Count()); } else { /* do something with op.Error */ } } VB Sub AllCustomersQuery() Dim query = New EntityQuery(Of Customer) manager.ExecuteQueryAsync(query, AddressOf CustomerQueryCallback) End Sub
Sub CustomerQueryCallback(ByVal op As EntityQueryOperation(Of Customer)) If (op.CompletedSuccessfully) Then Dim resultList = op.Results Console.WriteLine("Query returned {0} entities", resultList.Count()) Else ' do something with op.Error End If End Sub Catching errors and handling them properly is always important, especially for communications with a server. The server or network may go down at any time, so all server calls should have error handling to ensure your application doesn't crash. You can't wrap your asynchronous calls in a try/catch; you must handle errors explicitly via the operation object, a subject covered below. Do not call the Results property unless the operation has completed successfully. It is a mistake to ask for results when they are undefined. DevForce can't simply return null because null is a potentially valid query result. DevForce throws an exception instead. Many developers prefer to use a lambda callback for economy of expression: C# void AllCustomersQuery() { var query = new EntityQuery<Customer>(); manager.ExecuteQueryAsync(query, op => { if (op.CompletedSuccessfully) { var resultList = op.Results; Console.WriteLine("Query returned {0} entities", resultList.Count()); } else { /* do something with op.Error */ } }); } VB Sub AllCustomersQuery() Dim query = New EntityQuery(Of Customer) manager.ExecuteQueryAsync(query, Sub(op) If (op.CompletedSuccessfully) Then Dim resultList = op.Results Console.WriteLine("Query returned {0} entities", resultList.Count()) Else ' do something with op.Error End If End Sub) End Sub Completed event handlers Instead of a callback you may prefer to listen to the Completed event. In the next code sample, we simplify the query and call its ExecuteAsync method instead of the EntityManager's ExecuteQueryAsync. The principles and practices are the same as before. C# void AllCustomersQuery() { var op = manager.Customers.ExecuteAsync(); op.Completed += CustomerQueryCompletedHandler; }
void CustomerQueryCompletedHandler( object sender, EntityQueriedEventArgs<Customer> args) { if (args.CompletedSuccessfully) { var resultList = args.Results; Console.WriteLine("Query returned {0} entities", resultList.Count()); } else { /* do something with args.Error */ } } VB Sub AllCustomersQuery() Dim op = manager.Customers.ExecuteAsync() AddHandler op.Completed, AddressOf CustomerQueryCompletedHandler End Sub
Sub CustomerQueryCompletedHandler( _ ByVal sender as Object, ByVal args As EntityQueryOperation(Of Customer)) If (args .CompletedSuccessfully) Then Dim resultList = args .Results Console.WriteLine("Query returned {0} entities", resultList.Count()) Else ' do something with args .Error End If End Sub The handler takes an EventArgs instead of an operation coordination object but the effect is the same. You can use a lambda expression here too: C# void AllCustomersQuery() { var op = manager.Customers.ExecuteAsync(); op.Completed += (o, args) => { if (args.CompletedSuccessfully) { var resultList = args.Results; Console.WriteLine("Query returned {0} entities", resultList.Count()); } else { /* do something with args.Error */ } }; } VB Sub AllCustomersQuery() Dim op = Manager.Customers.ExecuteAsync() AddHandler op.Completed, Sub(o As Object, args As EntityQueriedEventArgs(Of Customer)) If args.CompletedSuccessfully Then Dim resultList = args.Results Console.WriteLine("Query returned {0} entities", resultList.Count()) Else ' do something with args.Error End If End Sub End Sub The operation coordination object The operation coordination object and the EventArgs have additional members. These differ depending upon the operation performed; consult the API documentation for details. BaseOperation reveals the members in common. Member Description CanCancel Get if this operation is cancellable. Cancel() Try to cancel the operation. It may (still) be cancellable. Cancelled Get if the operation was canceled before it completed. Completed Raised when the operation has completed, successfully or otherwise. A synonym for IsCompleted. CompletedSuccessfully Get if the operation completed without error and without having been cancelled. CompletedSynchronously Get if the operation was actually processed synchronously entirely on the caller's thread without a trip to the server. This happens frequently with query operations that are completely satisfied by cached entities. Error If the operation failed, this property returns the error as an Exception. Returns null if there is no error (yet). HasError Get if the operation failed with an error. IsCompleted Get if the operation finished, successfully or otherwise. A synonym for Completed. IsErrorHandled Get or set whether the error was handled. Error handling logic must set this to true; you usually call the MarkErrorAsHandled method. If the operation failed and this property is not true, DevForce re-throws the error, an exception that you cannot catch is likely to terminate the application. See error handling for details. MarkErrorAsHandled() Sets the IsErrorHandled property to true. PropertyChanged Raised when one of the operation's public properties has changed. UserState Get the custom object that carries information from the caller to the callback. Error handling We mentioned above that you can't wrap your asynchronous method in a try/catch. But you must still be on the lookout for errors, since any unhandled exception from an asynchronous operation will terminate the application. The exception raised by the asynchronous operation is returned in the Error property of the operation or EventArgs object. These expose the following properties useful in error detection and handling: CompletedSuccessfully - true if the operation completed without error and was not canceled HasError - true if there was an error Error - the exception, if there was an error IsErrorHandled- true if the error has been handled MarkErrorAsHandled- called to indicate the error has been handled and should not be rethrown It's your responsibility to inspect the operation object when the async operation completes and address failures. Mark the error "as handled" If an async operation completes with an error and you don't either call MarkErrorAsHandled or set IsErrorHandled to true, then DevForce will re-throw the exception. You won't be able to catch this re-thrown exception, and your application will crash. If you don't want the unhandled exception to terminate your application, you must: 1. Detect the exception by examining the async operation HasError property. 2. Process the exception as you see fit. The only viable option may be to log the exception or display a user-friendly error message. If a communication failure has occurred you can switch your application to offline mode. How you handle the error is up to you. 3. If the application should continue then mark the error as handled. Remember that users don't generally appreciate an application which crashes with obscure error messages. Here's our query completed handler from earlier with a bit more error handling: C# void CustomerQueryCompletedHandler(object sender, EntityQueriedEventArgs<Customer> args) if (args.CompletedSuccessfully) { var resultList = args.Results; Console.WriteLine("Query returned {0} entities", resultList.Count()); } else if (args.HasError) { HandleError(args.Error); // Your choice - log the error, show a message, ... args.MarkErrorAsHandled(); } } VB Sub CustomerQueryCompletedHandler( _ ByVal sender as Object, ByVal args As EntityQueryOperation(Of Customer)) If args.CompletedSuccessfully Then Dim resultList = args.Results Console.WriteLine("Query returned {0} entities", resultList.Count()) ElseIf args.HasError Then HandleError(args.Error) ' Your choice - log the error, show a message, ... args.MarkErrorAsHandled() End If End Sub IAsyncResult asynchronous pattern The EntityManager also supports the IAsyncResult asynchronous pattern through an explicit implementation of the IEntityManagerAsync interface . You will need to cast an EntityManager to this interface in order to use methods that follow this pattern. In the IAsyncResult pattern, an asynchronous operation named "SomeOperation" is implemented as two methods named BeginSomeOperation and EndSomeOperation
Batch asynchronous tasks with coroutines This topic describes how to batch multiple asynchronous tasks with coroutines. "Batching" means executing asynchronous operations as a group and waiting for the group to finish.
You can batch asynchronous tasks in parallel or serially. In a parallel batch, all tasks are launched at once; the caller is notified when they all finish or one of them fails. Use the DevForce Parallel Coroutine to create and manage a parallel batch. In a serial batch, the asynchronous operations are executed in order. The first operation must complete before the second operation can start; the third operation has to wait for the second operation to complete; so it goes until the last of the operations delivers the final result. Use the DevForce Serial Coroutine to manage a serial batch. Microsoft announced forthcoming C# and VB support for asynchronous programming - the "async/Async" and "await/Await" keywords - at PDC 2010. We expect this approach to obviate the need for Coroutines, perhaps parallel as well as serial. Meanwhile, you'll appreciate our Coroutine class and the style it fosters which is much like the "await"-style. Serial asynchronous batching background In the absence of coroutines, the obvious approach to sequencing a batch of asynchronous tasks is to build a chain of callbacks. You arrange for the first operation's callback to start the second operation ... whose callback starts the third operation ... until your final operation delivers the result in its callback. It works but the code is difficult to read and maintain. There are callbacks inside of callbacks inside of callbacks. They look like deeply nested "if ... else" statements. It's usually worse when you insert logic at every step to account for errors that might be thrown if one of the operations fails. The "Serial async challenge" topic explores the problem and this particular scenario in greater depth. Coroutines are a cleaner way to approach this problem. With Coroutines, we can write down a sequence of asynchronous tasks as if they were going to execute synchronously. They will actually run asynchronously but they will appear to be a series of synchronous calls, each of them blocking until it is finished ... as we'll see. Jeremy Likness wrote a nice blog post on the problem which he describes as follows: We have asynchronous calls that we want to process sequentially without having to chain dozens of methods or resort to complex lambda expressions. How do we do it? He goes on to explain Coroutines and how they address this problem. Finally, he points to several available Coroutine implementations. We looked at those implementations. We didn't love with any of them. Either they were too complicated or too closely coupled with particular UI technologies. Caliburn's "IResult", for example, depends upon an ActionExecutionContext with eight members, two of which are WPF/Silverlight specific (FrameworkElement, DependencyObject). That makes sense if you confine your use of Coroutines to the XAML UI client technologies. We wanted to support asynchronous programming in non-UI contexts and we wanted to rely on interfaces that were as small as possible. The DevForce Coroutine Class DevForce version 6.0.6 introduces the Coroutine class. We think you'll prefer our implementation for most - if not all - of your asynchronous programming needs. The Coroutine class resides in EntityModel for this release. It actually has no dependency on Entities; it lives here temporarily and will relocate to IdeaBlade.Core in a future release. Don't worry ... your code won't break. We'll use "type forwarding" so your original references will continue to work. Let's start with a simple example. Imagine that user must make calls to some "top customers". The sales manager prepared a list of "top customers" for each sales rep to call first thing in the morning. When the sales rep launches the application, the list of top customers should be on screen. Due to an unfortunate design decision, each "top customers" list has CustomerIDs but no relationship to Customers. You won't be able to get the customer information in a single query. You'll have to get the sales rep's "top customers" first, fish out the CustomerIDs, and use them in a second query to get the full Customer entities and the information to present on screen. That's two queries and the second one can't start until the first one finishes. You'll want to encapsulate the sequence in a single asynchronous method. Here is that method: C# // Coroutine caller private void LoadTopCustomers() {
var coop = Coroutine.Start(LoadTopCustomersCoroutine); coop.Completed += (sender, args) => {
if (args.CompletedSuccessfully) { ShowTopCustomers(coop.Result as IList<Customer>); } else { HandleError(args); }
} } Clearly LoadTopCustomers involves an asynchronous operation. You cannot completely escape the asynchronous nature of the process. On the bright side, there is exactly one asynchronous operation, the result of the static Coroutine.Start method. That method takes a companion coroutine that we wrote to handle the sequence of queries necessary to deliver the top customers. We'll get to that coroutine in a moment. Before we do, we note that when the coroutine completes ... if it completed successfully, we take its Result, cast it as a list of Customers, and give that to the ShowTopCustomers method which knows how to put those customers on screen. if the coroutine failed, we let the host's HandleError method figure out how to present the failure to the user. Move on to the coroutine, LoadTopCustomerCoroutine. This is the heart of the Coroutine business. C# // Coroutine Iterator private IEnumerable<INotifyCompleted> LoadTopCustomersCoroutine() {
var userId = CurrentUser.UserId;
var topCustOperation = Manager.Customers .Where(tc => tc.SalesRepID == userId) .Select(tc.CustomerIDs); // project just the CustomerIDs .ExecuteAsync());
var salesRepCustsOperation = Manager.Customers .Where(c => salesRepsTopCustIDs .Contains(c.CustomerID)) .ExecuteAsync();
yield return salesRepCustsOperation; // SUSPEND
// DONE... with list of Customer entities in Coroutine result yield return Coroutine.Return(salesRepCustsOperation.Results);
} Visual Basic developers can not code in this iterator style because VB.NET does not support iterators or the yield statement. Call the Coroutine with a function list instead; this approach uses iterators under the hood while shielding the developer from the yield statements. This alternative technique is also useful in many C# scenarios. Concentrate on the body of the method. There are only seven statements: 1. Get the sales rep's User ID from a static so we can select the top customers for this particular rep 2. Issue an async projection query for this sales rep's top customers. The "Select" clause tells you its a projection that will return a list of Customer IDs 3. Yield the query operation object from the ExecuteAsync() call. 4. Pour the query results (the Customer IDs) into a list variable, salesRepsTopCustIDs 5. Issue a second async query, one that returns every customer whose ID is in the salesRepsTopCustIDs list. 6. Yield the query operation object from the ExecuteAsync() call. 7. Yield (for the last time) the results of the sales rep's customer query. Reads like a normal procedure, don't you agree? Except for the yield return keywords. The linear flow, from top to bottom, is what we're after. If you had to add another asynchronous operation somewhere in the mix ... you'd have no trouble doing so. You'd simply follow the pattern you see here: write synchronously until you come to an asynchronous method. yield return the operation object result of each asynchronous DevForce method. at the very end, write: "yield return Coroutine.Return(the_final_result);" Passing Context into the Coroutine The co-routine iterator seems fully self-sufficient in our example. Look closer. Notice that it requires a userId to filter "top customers" and it needs an EntityManager. The userId comes from the static "CurrentUser" class and it gets an EntityManager from a convenient, ambient "Manager" property. What if you are not so fortunate? What if you think passing parameters via statics is (ahem) not the right architecture for you? You want the caller to provide "context" to the iterator from the outside, freeing the iterator from unnecessary dependencies. Let's rewrite the skeleton of our example to show how we might do that: C# // Coroutine Iterator private IEnumerable<INotifyCompleted> LoadTopCustomersCoroutine(ModelEntityManager manager, int userId) { // A
var topCustOperation = manager.Customers .Where(tc => tc.SalesRepID == userId) .Select(tc.CustomerIDs); // project just the CustomerIDs .ExecuteAsync());
yield return topCustOperation; // SUSPEND
// ... SAME AS BEFORE ...
}// Coroutine caller private void LoadTopCustomers(ModelEntityManager manager, int userId) {
var coop = Coroutine.Start( () => LoadTopCustomersCoroutine(manager, userId); // B
coop.Completed += ... // SAME AS BEFORE } The key is to (A) re-define the coroutine method to accept the context and (B) wrap the coroutine call in an "Action Lambda" before passing that into the Coroutine.Start(). Here are the two lines of interest. A) IEnumerable<INotifyCompleted> LoadTopCustomersCoroutine(ModelE ntityManager manager, int userId) B) Coroutine.Start( () => LoadTopCustomersCoroutine(manager, userId) ); Do not go wild with the parameters. If your iterator seems to need too many context parameters, the code is telling you to bundle them into a single construct - and a single concept. Figure out what it is. Make a type to carry that context and build an instance of it before you even get to the LoadTopCustomers method. Then pass this context object along the call chain. How does the DevForce Coroutine work? Many of you will recognize that this coroutine is a .NET Iterator. Iterators return IEnumerable<T> and consist of a sequence of statements punctuated by "yield return" statements ... just as we see here. For an introduction to the serial async task problem, iterators, and how DevForce Coroutines work, see The Serial Async Challenge topic. The Serial Coroutine Flow Diagram topic offers a visual rendition of the DevForce Coroutine behavior. Good-bye, AsyncSerialTask and AsyncParallelTask The DevForce AsyncSerialTask class was an earlier attempt to address these asynchronous batching problems. It has been deprecated in favor of the much easier Coroutine class. The same goes for the AsyncParallelTask which handled a related scenario. With AsyncParallelTask you could run several asynchronous tasks simultaneously and wait for a signal indicating that they had all finished. This capability is now subsumed by the Coroutine class; see its StartParallel method. We intend to remove both the AsyncSerialTask and the AsyncParallelTask classes from DevForce in the first half of 2011; both classes will be available in source code should you wish to continue using them ... and maintain them yourself. Serial async challenge This topic describes the difficulty of writing a sequence of asynchronous operations, each of which must wait for the prior operation to complete. Coroutines are the recommended remedy.
What's the big deal? In "desktop" .NET (console, Windows Forms, ASP, WPF applications) you have the option of writing a sequence of instructions that include calls to the server. The instruction following a server call waits - is "blocked" - until the server returns. Consider this schematic example. C# public void GetCustomerOrders() {
var custs = new List<Customer>(); GetCustomers(custs); // First server call for customers
var someCusts = SelectCustomersOfInterest(custs); // Local filtering
var orders = new List<Order>();
GetOrdersOfSelectedCustomers(someCusts, orders); // Second server call for orders
var someOrders = FilterCustomerOrders(orders); // local filtering
CustomerOrders = someOrders; // set a property for display } The SelectCustomersOfInterest method waits until GetCustomers() returns, having populated the "custs" list with the queried customers. Then it runs and produces the subset, someCusts. Then the GetOrdersOfSelectedCustomers method runs while FilterCustomerOrders waits. When the orders arrive, it filters them and sets the CustomerOrders property with the filtered orders. It may seem a bit contrived but this kind of thing happens frequently in user applications. You want to get some data from the server, process it a little, get some more data, process that a little, and then display the results on screen. You can't write code like this in Silverlight. Every trip to the server must be asynchronous in Silverlight. If you try write methods such as these, you soon discover that GetCustomers returns immediately with an empty list of customers. SelectCustomersOfInterest runs immediately - who know what it does with an empty customer argument. GetOrdersOfSelectedCustomers runs and instantly returns nothing. FilterCustomerOrders returns nothing. The screen shows nothing. Some time later, the server comes back with the initial batch of queried customers but, by that time, the GetCustomerOrders method has delivered an empty result and your user is confused. The retrieved customers are never processed. You don't get the selected orders. It's a disaster. That's why you've been learning about the DevForce asynchronous operations for querying, saving, and invoking server methods. You've learned, for example, that you could write the GetCustomers method something like this: C# private EntityQueryOperation<Customer> GetCustomers(List<Customer> customerList) {
// query for all customers var queryOperation = Manager.Customers.ExecuteAsync();
// upon success, pour the customers into the supplied customer list queryOperation.Completed += (s,args) => { if (args.HasError) { HandleTrouble(args); } customerList.AddRange(args.Results); };
// return the query operation so caller can wait return queryOperation; } Plug that back into the original example and GetCustomerOrders starts to look like: C# public void GetCustomerOrders() {
var custs = new List<Customer>(); var custOperation = GetCustomers(custs); // Get customers into list
} The good news is that SelectCustomersOfInterest won't start filtering the "custs" list until it actually has some customers in it. Although GetCustomerOrders still returns immediately - you can't escape the fundamental asynchronous nature of the process - at least you've got this part of the sequence behaving properly. The bad news is you've written a lot of tricky code and you are only half way home. You still have to get the orders for "someCustomers" - wait - and filter them before displaying them. Imagine we've written a GetOrdersOfSelectedCustomers in the same fashion as GetCustomers C# private EntityQueryOperation<Order> GetOrdersOfSelectedCustomers( List<Customer> customerList, List<Order> orderList) {
// get the ids of the selected customers var ids = customerList.Select(c => c.CustomerID).ToList();
// query for orders of customers who have ids in the selected id list var queryOperation = Manager.Orders .Where(o => o.ids.Contains(o.CustomerID)) .ExecuteAsync();
// upon success, pour those orders into the supplied orderList queryOperation.Completed += (s,args) => { if (args.HasError) { HandleTrouble(args); } customerList.AddRange(args.Results); };
// return the query operation so caller can wait return queryOperation; } Let's rewrite GetCustomerOrders to use it: C# public void GetCustomerOrders() {
var custs = new List<Customer>(); var custOperation = GetCustomers(custs); // Get customers into list
var custOperation.Completed += (s, args) => {
var someCusts = SelectCustomersOfInterest(custs); var orders = new List<Order>(); var orderOperation = GetOrdersOfSelectedCustomers(someCusts, orders);
orderOperation += (s, args) => {
var someOrders = FilterCustomerOrders(orders); CustomerOrders = someOrders; // set property for display } };
} We've got logic distributed over several helper methods and two levels of indentation in our master method. Notice the "Staircase Effect" and imagine if we had to introduce yet another asynchronous operation. When is it done? How does the caller of GetCustomerOrders know when all operations have completed successfully? In other words, how does the caller know when the CustomerOrders property has been set? Answer: it can't know. Our GetCustomerOrders method has no mechanism for telling the caller when all operations are complete. Could you change the signature to return something that the caller could use? Think about what that would be. Could you return the first operation object (custOperation)? You could but it wouldn't solve the problem. The caller could listen for when the customer query completed. But that is only the first step in the sequence. The caller wants to know when the last operation completes. Could you change the signature to return "orderOperation" which is the last operation object? No ... you cannot. The "orderOperation" object isn't defined until after you've retrieved customers. GetCustomers must return something immediately; there is no way to stall until we've defined the orderOperation object. What if one of the operations throws an exception? The GetCustomers and GetOrdersOfSelectedCustomers methods intercept errors via a "HandleTrouble" method. Where did that method come from? Probably from the caller. How did it get into our helper methods? Looks like our query methods are tightly coupled to the caller. That's not going to work long term. To maintain these methods properly and even consider reusing them, we have to disentangle them from the class that happens to be calling them right now. Face it. The query methods shouldn't know what to do about errors. They shouldn't have specific knowledge about the caller. We want our async methods to propagate errors back to the caller (who should know what to do with them) in some general, de-coupled way. Bring on the Coroutine We're about to describe the DevForce Coroutine in prose and code. Check out the Serial Coroutine Flow Diagram topic for a more visual rendition of the same subject. Here is the same example we considered above, written this time with the DevForce Coroutine. C# // Coroutine Host Method public CoroutineOperation GetCustomerOrders() { return Coroutine.Start(CustomerOrdersLoader); }
// Query for every customer in the database var custOperation = Manager.Customers.ExecuteAsync();
yield return custOperation; // SUSPEND
custs.AddRange(custOperation.Results);
// Reduce to an "interesting" subset of all customer by // filtering in some undisclosed fashion var someCusts = SelectCustomersOfInterest(custs);
// Extract the ids of the selected customers var ids = someCusts.Select(c => c.CustomerID).ToList();
// Query for any order with a customer whose id is in that id list var orderOperation = = Manager.Orders .Where(o => ids.Contains(o.CustomerID)) .ExecuteAsync();
yield return orderOperation; // SUSPEND
var someOrders = FilterCustomerOrders(orders); CustomerOrders = someOrders; // set property for display
} First note that the Coroutine.Start method returns a CoroutineOperation object. This is a derivative of the DevForce BaseOperation class ... just like returned value of every other DevForce asynchronous method. Yes ... the Coroutine is itself an asynchronous operation. It is special in that it bundles into a single package a collection of many asynchronous operations (aka, "tasks"). Life is much easier for the Coroutine caller which need only wait for a single Coroutine completion event ... rather than cope with the completions of many individual tasks. We can't see the caller in this example ... the "thing" that calls GetCustomerOrders. But we should expect this caller to add an event handler to the CoroutineOperation's Completed event ... a handler that will take care of business when the Coroutine reports "all tasks completed." Let's turn our attention to what we can see - the Coroutine.Start and the CustomerOrdersLoader that is passed to it as a parameter. The "Co" in "Coroutine" implies at least two cooperating actors. The two actors are (1) the "Provider" and (2) the "Consumer". The "Provider" is your code that performs one task after another. Some of the tasks are synchronous, some of them asynchronous. CustomerOrderLoader is the Provider in this code sample. As you can see, CustomerOrderLoader does a little synchronous work (e.g., variable initialization) for awhile. Then it hits an asynchronous method at which point it "yields" to its partner, the "Consumer". What does the Producer yield? It yields the result of the asynchronous method. That result happens to be a special kind of "Coroutine coordination object" with information about the asynchronous task currently underway. A Cooroutine coordination object must implement the DevForce INotifyCompleted interface. Every DevForce asynchronous method returns an object that implements INotifyCompleted. Therefore, every DevForce asynchronous method returns an object that can both participate in Coroutine processing and report the status and outcome of an async operation. To whom does the Producer yield? It yields to the DevForce Coroutine class. Actually, it yields to a hidden DevForce "Consumer" that receives the "coordination object". While you don't actually see the Consumer ... or interact with it directly... it's there, the invisible partner to your Producer co-routine. The Consumer takes the Producer's "coordination object" and immediately injects its own callback method. This is the method that the "coordination object" will call when the asynchronous operation completes. Then the Consumer method let's go. At this point, both the Consumer and the Producer are suspended. Technically, they aren't running at all. They are methods which, seemingly, have done their work and finished. But both the DevForce Consumer and your coroutine Producer are poised to resume work when the asynchronous operation completes. When that async operation completes, it raises a Completed event on its operation result object. We just saw that this "operation result object" is also a Coroutine coordination object. In that capacity, it also invokes the callback method that the Consumer injected. That injected callback method effectively revives the Consumer. The Consumer examines the outcome of the asynchronous operation. If the operation completed successfully, the Consumer, calls upon the Producer, asking the Producer for the next "coordination object". The Producer method, instead of starting over, picks up where it left off. In our code sample, it picks up just after the first "yield return" where it adds the results of the customer query to the "custs" list. The Producer continues executing until it comes to the next asynchronous operation, the query for Orders. Then it yields a second time, returning a different "coordination object" to the Consumer, the operation object returned by the Orders query. Again, the Consumer injects its callback into the coordination object and both Consumer and Producer are suspended again ... Until the Orders query completes. And when it completes, the coordination object calls the Consumer, the Consumer sees that everything is still ok and calls the Producer. The Producer resumes where it left off, filters the Orders, and sets the caller's CustomerOrders property. This time there is no more work to do. There are no more asynchronous operations and no more "yield return" statements. So (implicitly) the Producer tells the Consumer, "I'm done." The Consumer, realizing that there are no more tasks, wraps up and raises the Completed event on its CoroutineOperation object. Do you remember that object? That was the object returned by GetCustomerOrders. As we noted above, the GetCustomerOrders caller added its own handler to the CoroutineOperation. That handler kicks in and does whatever needs doing now that all of the asynchronous tasks in the Producer co-routine have finished. An Iterator Tutorial The co-routine dance works well because we write the task logic in a linear fashion, much as we write a fully synchronous procedural method. Visual Basic developers can not code in this iterator style because VB.NET does not support iterators or the yield statement. Call the Coroutine with a function list instead; this approach uses iterators under the hood while shielding the developer from the yield statements. This alternative technique is also useful in many C# scenarios. The kicker is that "yield return" statement which suspends the Producer while the asynchronous operation works in background. The "yield return" syntax is the tell-tale sign of an "Iterator". The iterator is the key to the async coroutine pattern. You've undoubtedly used an iterator but you may not have known it and may never have written one. Iterators are not a DevForce invention. They've been around in the literature for a long time. Iterators were added to .NET in version 2.0. They're a mechanism for generating values on demand. Here's an example: C# public IEnumerable IntegerIterator(int start, int end){ for (int i = start; i <= end; i++) { yield return i; } } You usually consume it with a foreach expression like so: C# foreach (int n in this.IntegerIterator(1, 10)){ System.Console.Write(n + " "); } // Output: 1 2 3 4 5 6 7 8 9 10 A C# foreach takes something that produces an IEnumerable and "iterates over it" , by which we mean, it repeatedly asks the IEnumerable for a next value until the IEnumerable has no more values to give. The IEnumerable could be a collection of values. Or, as in this case, it could be a producer of values. Iterators can produce values in all kinds of ways. Here's one way that is similar to what you've seen in a coroutine: C# public IEnumerable StringIterator(){ yield return "My dog"; yield return "has"; yield return "fleas"; } We can consume it with a foreach just as we did the IntegerGenerator: C# foreach (int str in this.StringIterator()){ System.Console.WriteLine(str); } // Output: My dog // has // fleas The foreach calls our StringIterator three times because there are three yields. The StringIterator "remembers" where it last yielded. The first call yields "My dog" ... and StringIterator positions itself so that the next time it is called, it yields "has". And the next time it yields "fleas". And the next time it reports "I've got nothing else to give you". The iterator is suspended after each yield. By "suspended" I mean that the iterator stops. I do not mean that the thread is suspended or that the iterator is "waiting". The iterator is "done" in much the way that any other method you call is done when it hits a return statement. But there is a crucial difference. Any other method would start from its beginning. When you call the iterator, it resumes where it left off. The iterator maintains a snapshot if its current state and a bookmark of the execution step where it yielded. When called again, it goes to that bookmark, revives the saved state, and continues from there. Mixing regular and yielding statements The iterator may be composed with a combination of regular and "yield return" statements. We can rewrite the previous iterator as follows; it produces exactly the same output. C# public IEnumerable StringIterator(){
var myDog = "My dog"; yield return myDog;
var has = "has"; yield return has;
var ignored = "you'll never see this"; var pulgas = "fleas"; yield return pulgas; } After each call, the iterator evaluates the next series of statement until it encounters a "yield return". This iterator returns three strings as it did before but this time, each string takes two or three statements to product. The ability to interleave regular and yielding statement is critical to the asynchronous Coroutine pattern. In real world applications, our iterators need to mix synchronous statements with asynchronous statements that yield until the async operation returns. Quitting an iterator early Your iterator logic may require you to terminate an iterator early, ignoring the remaining statements. The pertinent syntax is "yield break" as shown here: C# public IEnumerable StringIterator(){
yield return "My dog"; yield return "has";
yield break; // Ignore everything below this point
yield return "fleas"; // never executed }
// Output: My dog // has
Serial coroutine flow This topic diagrams the flow of control for a serial coroutine call. The actors in the diagram are:
Caller: your code that calls the Coroutine.Start method and monitors the CoroutineOperation object (shown in the diagram as "Coroutine Result") Producer: your code that provides the sequence of synchronous and asynchronous statements you want executed as a single unit of work. It's the primary argument your caller provides to the Coroutine.Start method. If you write ,Coroutine.Start(LoadCustomerInfoCoroutine), then LoadCustomerInfoCoroutine is your Producer. The Producer is usually implemented as a .NET Iterator. After each asynchronous operation, your iterator yields a DevForce INotifyCompleted object such as the OperationBase- derivative returned by a DevForce asynchronous operation. For example, if the asynchronous task were a query such as Manager.Customers.ExecuteAsync(); your iterator would yield return the EntityQueryOperation<Customer> object returned by that query method. Consumer: A DevForce Coroutine "Consumer" co-routine works with the "Producer" to move forward through the sequence of asynchronous serial tasks. The Consumer and Producer are partners: the "Consumer" manages the flow, the "Producer" provides the tasks. You won't actually see the Consumer coroutine ... it's a hidden by-product of the DevForce Coroutine.Start(producer) call. But it's helpful to know that something is there, working with your Producer to make the magic happen. You will see numbered balls in the diagram. These are the INotifyCompleted objects (aka, "Coordinator" objects) that are passed around among the Caller, Consumer and Producer. The asynchronous operations themselves are also represented by the numbered balls; when they complete, they make changes to the Coordinator objects such as setting the results, error flags, cancellation flags ... and most importantly, they both raise the Completed event and invoke the Consumer callback function that tells the Consumer to take the next evaluation step.
A Serial Coroutine Story in Pictures The Caller asks the Coroutine to start. It hands back a CoroutineOperation object (aka "Coordinator") containing information about task processing and, ultimately, the disposition of the asynchronous operations - the tasks - encapsulated by the Coroutine "Producer". The CoroutineOperation object has a Completed event. The Caller typically adds a handler to this Completed event; the handler will know what to do when all of the co-routine tasks have finished.
The DevForce Coroutine class consumes notifications yielded by the "Producer". It commands the Producer to give it the next notification. Typically, a Producer is an Iterator function that returns an IEnumerable<INotifyCompleted>. The consumer extracts from this function an Enumerator (an implementor of the IEnumerator interface) which offers "MoveNext()" and "Current" members. These members combine to fulfill the "Get next" command semantics.
The Producer does some work. It encounters an asynchronous method call. It yields the OperationBase object returned by the asynchronous method (e.g., an EntityQueryOperation<Customer> object from a query such as Manager.Customers.ExecuteAsync()). OperationBase implements INotifyCompleted and is thus the kind of "Coordinator" object that the DevForce Coroutine Consumer expects. Note that the asynchronous operation has not completed yet (it's still gray). The Producer is done for the moment. It is suspended until the Consumer asks for the next INotifyCompleted object.
The DevForce Coroutine Consumer adds the received INotifyCompleted object to the CoroutineOperation.Notifications collection. The DevForce Coroutine Consumer stops at this point. In effect it suspends further processing It won't ask the Producer for the next INotifyCompleted object .. until "reawakened" by a callback from the "Coordinator" object - the INotifyCompleted object just yielded by the Producer. The application continues to run. The two co-routines - Producer and Consumer - are poised to continue to the next task. But neither makes a move until the pending asynchronous operation completes ... and calls the Producer.
The asynchronous operation finishes at last, raising its Completed event and signaling success (It's green!). Because the asynchronous operation object implements INotifyCompleted, it also calls back to the DevForce Coroutine Consumer. The reawakened Consumer sees that the asynchronous operation returned successfully ...
... so the Consumer asks the Producer for the next INotifyCompleted "Coordination" object.
The cycle repeats: The Producer does some work until it encounters another asynchronous function. It yields that asynchronous function's returned value, an INotifyCompleted object. The Consumer adds that object to the Notifications list of the CoroutineOperation object (shown as "Coroutine Result" in the diagram) The asynchronous operation eventually succeeds, raises its Completed event and calls the Consumer to tell it about that success.
After three more cycles the Producer indicates that has no more Notifications to yield (the MoveNext() method returns false).
The DevForce Coroutine Consumer raises its own Completed event. The Caller has been listening for this event and now knows that the Coroutine has completed all of its tasks successfully. More precisely, the handler that the Caller attached to the CoroutineOperation's Completed event is ready to interpret the outcome of the co-routine tasks. The handler can inspect the CoroutineOperation object's Notifications collection and examine any of the asynchronous tasks that were completed. Usually it only cares about the last one. The handler will often extract results from the last completed operation or perhaps from the CoroutineOperation object itself. It's really up to the handler what happens next; the co-routines have done their jobs.
Failure Perhaps something went wrong with the second asynchronous operation. It raises its Completed event but its EventArgs indicate an error. The asynchronous operation calls back to the DevForce Coroutine Consumer - just as it would if the operation had succeeded.
The DevForce Coroutine Consumer sees the error. It observes that the error was not handled* so it concludes that it should stop asking the Producer for more notifications. It won't call the Producer's MoveNext() method again. Instead, it raises the Completed event of the CoroutineOperation object ("Coroutine Result" in the diagram). The Caller's handler is listening to that event. The Caller's handler sees that there was a failure and must decide what to do next. If the Caller's handler neglects to call "MarkAsHandled()" on the CoroutineOperation object, the DevForce Coroutine will throw the exception.
* The"Producer" could have attached its own Completed handler to the asynchronous operation object. The Producer's Completed handler gets the first look ... before the Coroutine sees the Completed event. If the Producer's handler marks the asynchronous operation object as handled (e.g., by calling MarkAsHandled()), then the Coroutine Consumer will resume the process and ask the Producer for the next Notification object. Events versus Callbacks We've told this story as if Completed events and event handlers were "the way to go". Most developers prefer events. But the Coroutine and asynchronous methods all support callback delegates. You are welcome to use callbacks instead of the Completed events.
Parallel async coroutines Use a parallel asynchronous coroutine when two or more asynchronous tasks should run simultaneously and you want to be notified when all of them have completed ... or if any one of them fails.
We've focused on batching Asynchronous Serial Tasks because it's a common pain point that cries out for some relief. Having slain that dragon, you discover another one in the same cave. You have several asynchronous tasks that must finish collectively but they are mutually independent and you don't care which finishes first. You suspect that your application might run faster if you fired them off together and let the caller work on something else until all of the parallel tasks are done. You suspect ... but you do not know. The watch word for performance tuning is "measure.". Measure before and after. Measure for different runtime environments. We have seen plenty of multi-threaded code that ran slower than the original single-threaded approach. While it is often the case that a batch of IO tasks, such as fetching entities from a remote database, complete significantly faster when run in parallel, we urge you to test that assumption in your application. A parallel scenario: loading lists at launch Many applications rely on lots of small lists: lists of states or provinces, lists of status codes, lists of suppliers, etc. Most are very stable (the provinces) and might be worth enshrining in the code. However, you have your good reasons to read them in from the database when the application launches. Your application will not function properly until all of the lists are loaded. You don't care are about load order; you just want to hold back the user until they've all arrived. You noticed that waiting for the server to deliver these lists one by one is painfully slow. The size of each list is not the issue - they are all tiny. It's the number of server trips that is killing responsiveness. Most of the overhead is in managing the conversation with the server, not in database query or in the transmission of data. Your client is idle while you wait for each list result. Maybe you can speed things up by firing off a bunch of requests at once. You could be right about that. You're first thought is to pull down everything you need in a single query. Sometimes you can do that .. .see "Retrieving Many Types in a Single Query" below. More often you can't. There is probably no relationship - neither inheritance nor navigational - among the entities you want to retrieve. You have to issue separate queries for each list. Coroutine.StartParallel() The DevForce Coroutine class is a good solution to this problem. It operates much like serial asynchronous batching except you'll call Coroutine.StartParallel (coroutine) instead of Coroutine.Start (coroutine). Two kinds of Coroutine are quite similar. An individual serial asynchronous task looks like an individual parallel asynchronous task. A batch of asynchronous serial tasks looks much like a batch of asynchronous parallel tasks. A producer of asynchronous tasks (such a the coroutine iterator) yields the same kind of INotifyCompleted objects. The critical difference - and it is critical - is that parallel tasks cannot not share their data with each other. They must be completely independent of each other - you can't use the result of one as the input to another - and you should not care whether one finishes before the other. This difference aside, batches of asynchronous serial and parallel tasks are structurally the same. In DevForce you define a batch of tasks the same way: with a co-routine that returns an IEnumerable<INotifyCompleted>. Here's an example: C# // Coroutine parallel task iterator private IEnumerable<INotifyCompleted> LoadListEntitiesCoroutine() {
yield return Manager.Employees.ExecuteAsync(); // only 7 in Northwind yield return Manager.Regions.ExecuteAsync()(); yield return Manager.Territories.ExecuteAsync()(); yield return Manager.Suppliers.ExecuteAsync()();
// not in Northwind but you get the idea yield return Manager.StatesProvinces.ExecuteAsync()(); yield return Manager.Colors.ExecuteAsync()();
} Each line launches an asynchronous query and yields the query's coordinating "Operation" object result. Once you've defined a batch of tasks, you need some collaborating component to run them for you, to decide when the batch is done, and to redirect failures to your error handling code. We've been calling this the DevForce Coroutine "Consumer". We know what happens if we run this co-routine iterator with the Serial Coroutine Consumer by calling Coroutine.Start(LoadListEntitiesCoroutine): the first query runs, wait, return, then the next one runs, wait, return, and so forth. That's not what we want. We want "start the first, and the second, and the third ... and the fifth; now wait until they finish; then tell me about it." And that's what you get when you call Coroutine.StartParallel(LoadListEntitiesCoroutine). The DevForce Parallel Coroutine Consumer immediately walks through the entire iterator launching all of the asynchronous tasks at once. The iterator yields INotifyCompleted objects (just as we did in the serial Coroutine example). With each yield, the Parallel Consumer identifies an individual asynchronous task to watch. It builds a list of these yielded values as they arrive (see the Notifications collection below). Happy side-effects of a parallel coroutine We may not care about the result of our parallel tasks. We might be content with their side- effects. In this example, we're filling the cache with entities that we'll use later. That may be all we need to do. Imagine that we subsequently query for Orders. We want to know which "employee" was the sales rep who sold that order. The statement anOrder.Employee will find the related employee available in cache. You won't have to worry about making an asynchronous trip to the server to get the employee ... because it is waiting for you in cache. This approach is popular ... for good reason. It's difficult to write a stable, responsive UI that could fly off to the server any minute for missing data. Development is much easier if you can set up with everything you need - pay the asynchronous management price up front - and then operate locally for as long as possible. If you adopt this approach, it is a good idea to disable the "lazy load" feature by setting the EntityManager's DefaultQueryStrategy to CacheOnly. Manager.DefaultQueryStrategy = QueryStrategy.CacheOnly; You can still query the database for specific information at any time. Just remember to specify the "go to server" QueryStrategy when you need to make that trip. For example, when we want to get fresh information about a particular customer, we could write: C# Manager.Customers .Where(c => c.CustomerID == someID) .With(QueryStrategy.DatabaseThenCache) // go to database first, then check with the cache .ExecuteAsync(yourCallback);
// Do other stuff The phrase that matter is: .With(QueryStrategy.DatabaseThenCache) Results of a parallel coroutine On the other hand, you might want to do something with the parallel task results as soon after they are all arrive. A good place to do that is in your parallel task caller ... more precisely, in a callback method or Completed event handler that you established in your parallel task caller. C# // Coroutine caller private void LoadListEntities() {
var coop = Coroutine.StartParallel(LoadListEntitiesCoroutine);
// Although order of finish is indeterminate, // the order of notifications matches the Coroutine yield order // Simplistic approach for demo purposes.
private IList<T> MakeList<T>(INotifyCompleted completedArg) { var op = completedArg as EntityQueryOperation<T> ; return (null == op) ? new List<T>() : new List<T>(op.Results); } The Notifications collection The highlight of the code example is the Notifications collection. The Notifications collection is the vehicle for processing task results. The DevForce Coroutine Consumer adds each value yielded by your co-routine Producer (your iterator) to this collection in the order received. You access the Notifications collection from either the CoroutineOperation and CoroutineCompletedEventArgs. Such tracking of yielded INotifyCompleted values works for the Serial Asynchronous Coroutine as well. Only the timing differs. In the serial case, the yielded values appear in the Notifications collection over time. They'll all get there eventually if the serial Coroutine succeeds. In the parallel case, they are available immediately. Retrieving Many Types in a Single Query Parallel queries are ideal when you have to make a lot of small queries simultaneously. But each query is its own trip to the server, even when you parallelize them. If you restructured your database a little bit, you might be able to reduce many of these trips to a single trip. We're making a digression into "Polymorphic Queries". The intent is the same - to retrieve a variety of entities at the same time. That's why we're discussing it here. But the technique has nothing to do with parallel queries. Maybe you could combine certain kinds of entity types into a single physical table. Then you could define a common base entity class for that table, model the specific entity classes as sub-types of the base entity class, and issue a single DevForce "polymorphic query" defined for the base entity. This will pull down all of the derived entities at once. We've seen this approach used to good effect with Code entities. Codes often share the same structure: {Id, CodeName, Description}. You can often store hundreds of different codes in a single Code table {Id, CodeType, CodeName, Description} and discriminate among them by the "CodeType" field. You define an abstract base type, "Code", and use Entity Framework Table-per-Hierarchy (TPH) to model the hundred distinct code entity types as derivatives of the Code entity. Start your DevForce application, query for all "Codes", ... and all of the distinct code-like entities arrive in cache in a single query. This can be extremely effective ... if you're comfortable with it. Not everyone is; some folks want the database foreign key constraint to prevent accidentally setting the Order with a Color code instead of a Shipping code. You have to decide if this is a significant risk for your application.
Asynchronous errors This topic discusses asynchronous operation error handling. The specific context is coroutine errors but the analysis and discussion apply to all DevForce asynchronous methods.
Errors can arise anywhere in the process. It's not easy to manage exceptions thrown by asynchronous operations. The Coroutine is asynchronous as are many of its tasks. Silverlight is an asynchronous world. You have to develop effective exception handling practices to survive in that world. Your synchronous programming instincts lead you in the wrong direction. Your first thought is to wrap the Coroutine.Start() call in a try ... catch like so: C# // Coroutine caller private void LoadTopCustomers() {
// more code } That won't work. Remember that the LoadTopCustomers method completes almost immediately. It finishes long before the first asynchronous Customer query returns. If the query fails - perhaps because the connection to the server is broken - the server exception arrives much later, packaged inside the query operation object (e.g., the EntityQueryOperation<Customer> object). You cannot predict when the query will returns. What you do know - for certain - is that if you don't detect and handle the exception reported by the query, DevForce will re-throw the exception on the client. You won't be able to trap that exception because you have no clue where it will be thrown. In practice, it will bubble up to your application's "unhandled application exception" logic (in App.xaml.cs by default) where your only viable option is to log the error and terminate the application. Not good. You can prepare for possible error in either of these two ways: C# private void LoadTopCustomers_with_Completed_event_handler() {
// more code } This puts you on the right road. It may not be quite enough; you better call MarkErrorAsHandled, for example. But maybe you are already doing that in the HandleError method, in which case you are in good shape. If you are not sure, read on. DevForce Async Error Interception DevForce offers a helpful variety of error handling facilities in the Coroutineclass and in the other DevForce asynchronous methods. In all cases, exceptions detected within an asynchronous operation are captured in the async method's "Operation" object - the async operation coordination object returned by an ExecuteAsync() for example. All async methods return an async operation coordination object that inherits from BaseOperation . All BaseOperation classes and their associated asynchronous operation EventArgs expose the following properties: CompletedSuccessfully - true if the operation completed without error and was not canceled HasError - true if there was an error Error - the exception, if there was an error Cancelled- true if the operation was canceled. It's your responsibility to inspect the BaseOperation or EventArgs when the async operation completes and address failures ... as we did in the original version of LoadTopCustomerCoroutine. Your first opportunity to catch an asynchronous operation error is inside your iterator coroutine. C# // Coroutine Iterator private IEnumerable<INotifyCompleted> LoadTopCustomersCoroutine() {
var userId = CurrentUser.UserId;
var allCustOperation = Manager.Customers.ExecuteAsync( // Callback checks for error op => { if (op.HasError) HandleError(op); } );
// another way to catch errors var allCustOperation.Completed += (s1, args1) => { if (args1.HasError) HandleError(args1); };
yield return allCustOperation; // SUSPEND
// more code
} Note that we added a callback method to the query's ExecuteAsync - a callback that checks for the error and calls an appropriate method. We also demonstrated an alternative, "event handler" approach that does exactly the same thing. Then we yield. The DevForce Coroutine Consumer receives the yielded operation object and adds its own handler to the allCustOperation's Completed event. When the query operation returns, the coroutine callback gets the first crack at interpreting the results. Then the coroutine Completed event handler gets the next look. The outer Coroutine Consumer gets the last look. Note that we arranged to inspect the results before yielding to the DevForce Coroutine "Consumer". The following example mistakenly adds error interception after the yield. ( C# // Coroutine Iterator private IEnumerable<INotifyCompleted> LoadTopCustomersCoroutine() {
var userId = CurrentUser.UserId;
var allCustOperation = Manager.Customers.ExecuteAsync();
yield return allCustOperation; // SUSPEND
// Too late. If there was an error, you'll never get here if (allCustOperation.HasError) HandleError(allCustOperation);
// more code
} This won't work the way the author intended. The DevForce Coroutine Consumer will have already inspected the result before giving control back to your coroutine. IF the query reported an error, DevForce would have discovered that error and (by default) will have terminated the coroutine immediately. The coroutine error handling code will not run. Do Not Touch Results Of An Errant Or Canceled Operation Many async operation coordination objects and their associated EventArgs have a "Result" or "Results" property. The CoroutineOperation and CoroutineCompletedEventArgs have a "Result" property. Such properties are undefined it the operation has an error or was canceled. You will get an exception if you attempt to access them. Mark-As-Handled It's OK to let the query error bubble up to the outer Coroutine. That may be the best place to handle an error. But that should be a deliberate decision on your part. You must be aware of the ramifications of that decision ... which means you must know how the DevForce Coroutine Consumer handles task errors and cancellations. Most important: you must understand the significance of marking - or failing to mark - an error "as handled". There is an IsHandled flag on every async operation object and EventArgs; the flag is not always visible but it is there. If the async operation completes with an error and you don't set the IsHandled flag to true somewhere, somehow, then DevForce will re-throw the exception. You may have examined the exception. You may think you've addressed the exception. But unless you set IsHandled to true, DevForce has to assume that the exception was unexpected and re-throw it. Your application is going to crash if DevForce re-throws an async operation exception because there is no way for you to trap it. You do not want DevForce to re-throw that error. You want to be in control. And you can be. Before we get to how, let's explain why we re-throw it. Note that we cannot tell if you dealt with the exception or if you missed it. Exceptions indicate trouble. It may be trouble you can anticipate and recover from ... in which case you should do so ... and tell DevForce that you did so. But unhandled exceptions are fatal. The cause does not matter. You should not continue in a potentially unstable and errant state. The only appropriate course of action is to terminate the application. If you don't want DevForce to terminate your application, you must: 1. Detect the exception by examining async operation results 2. Process the exception as you see fit 3. If the application should continue ... you must call MarkErrorAsHandled, a method on every DevForce async operation and EventArg. There are several opportunities to call MarkErrorAsHandled ()). This may be best understood by exploring some scenarios. Scenario #1: MarkErrorAsHandled not called 1. The DevForce Coroutine Consumer calls your Iterator 2. Your iterator issues an async query 3. Your iterator adds error interception logic (either with a callback or a Completed event handler) 4. Your iterator yields the query operation coordination object to the Consumer ... and the co- routines suspend 5. The query fails on the server and returns the server exception 6. DevForce raises the Completed event on the operation coordination object yielded in step #4. The EventArgs contain the server exception which is also accessible directly from the operation object itself. 7. Your iterator interception logic, which is either a callback or event handler, examines the exception 8. The DevForce Coroutine Consumer sees the exception. 9. The DevForce Coroutine Consumer sees that the exception is not marked as handled ... and terminates the process. It will not ask your iterator for the next yield. 10. The DevForce Coroutine adopts the query exception as its own exception. Now the Coroutine has an error. 11. You didn't intercept exceptions on the Coroutine either. 12. DevForce concludes that the exception is unhandled and re-throws it. 13. Your application crashes. Scenario #2: MarkErrorAsHandled called inside the Iterator 5. The query fails on the server and returns the server exception 6. DevForce raises the Completed event on the operation coordination object yielded in step #4. 7. Your iterator interception logic, which is either a callback or event handler, examines the exception 8. That interception logic calls MarkErrorAsHandled() 9. The DevForce Coroutine Consumer sees that the exception is handled 10. The DevForce Coroutine resumes calling your coroutine iterator which picks up with the next step. Scenario #3: MarkErrorAsHandled called in the Coroutine's operation coordinator 5. The query fails on the server and returns the server exception 6. DevForce raises the Completed event on the operation coordination object yielded in step #4. 7. By design, you do not have any error interceptor logic in your iterator. 8. The DevForce Coroutine Consumer sees that the exception is not handled ... and terminates the process. It will not ask your iterator for the next yield. 9. The error bubbles up to the Coroutine operation coordinator (the result of Coroutine.Start()) 10. You have added error interception logic to that operation coordinator object as we showed at the topic of this topic. 11. That interception logic calls MarkErrorAsHandled() 12. DevForce see that the exception is handled ... and permits your application to continue. Error in the Coroutine Itself Your coroutine consists of synchronous and asynchronous statements. Perhaps all of your async operations succeed ... or would have succeeded. But, sadly, your coroutine threw an exception. You probably won't see this exception right away ... not unless it occurs before the first yield return. Therefore, you can't rely on a try...catch to guard against this kind of exception either. Your coroutine is not supposed to throw exceptions but it happens. The outer DevForce Coroutine Consumer will catch it. You could not have handled that exception inside the iterator (otherwise, the Coroutine iterator would not have failed) so the Coroutine must terminate the iterator. But DevForce will propagate the exception to the Coroutine operation object. Your error handling at that level can decide what to do. You can MarkErrorAsHandled and permit the application to continue running if that makes sense. Parallel Async Coroutine Error Handling The reasoning and behavior are essentially the same whether you use the Serial Async Coroutine or the Parallel Async Coroutine. If you don't handle an exception at some level, DevForce will re-throw it and your application will most likely crash. If a coroutine async task results in a handled exception (you called MarkErrorAsHandled, both Coroutines continue processing the remaining tasks. The only difference - and it is slight - is how DevForce treats the remaining tasks when your iterator produces an unhandled error. The Serial Async Coroutine will stop processing your iterator coroutine the moment it sees a task with an unhandled exception. It won't run any of the remaining tasks. In the Parallel case, some or perhaps all of the asynchronous tasks could be running when the unhandled exception arises. DevForce will try to cancel all outstanding tasks, report them as canceled, and will ignore their results even if it can't cancel them. Tasks that already completed will retain their "completed successfully" status and their results remain available.
Using function lists This topic describes how to batch multiple asynchronous tasks with coroutines using function lists. You will likely use this technique when writing coroutines in Visual Basic.NET. It's also useful in C# when you want to build up a list of things to do asynchronously rather than define all those tasks within a single iterator method.
Coroutines in VB.NET The iterator approach to batching asynchronous tasks is a popular choice in C#. That choice is not available to Visual Basic.NET programmers because iterators aren't implemented in VB.NET. Fortunately, you can still use the DevForce Coroutine class in VB to manage a sequence of asynchronous tasks. Instead of writing an iterator to produce the sequence, you construct a list of functions that return INotifyCompleted and pass the list to the Coroutine. We call this the "function list approach" to coroutines. The function list approach is also useful in C# when you want to build up a list of things to do asynchronously rather than define all those tasks within a single iterator method. Serial coroutine with function list The basic recipe is as follows 1. Write a "batcher" function that builds a list of asynchronous functions, each returning an INotifyCompleted object. 2. Call the Coroutine.Start method, passing in the list returned by the batcher. 3. Either include a callback method or attach a handler to the Coroutine.Completed event. Here is an example that loads all customers, updates them, and saves them back to the database. Write the "batcher" LoadAllCustomersUpdateAndSaveCore is the "batcher" in our example. C# private IEnumerable<Func<INotifyCompleted>> LoadAllCustomersUpdateAndSaveCore(NorthwindEntities manager) { // List of asynchronous functions for the Coroutine to execute serially. var funcList = new List<Func<INotifyCompleted>>();
// Get all customers funcList.Add(() => manager.Customers.ExecuteAsync());
// Make a change to them and save them funcList.Add(() => { UpdateAllCachedCustomers(manager); // sync operation to update customers in cache - for example return manager.SaveChangesAsync(); // asynchronous operation returning INotifyCompleted });
// return the list return funcList; } VB Private Function LoadAllCustomersUpdateAndSaveCore(ByVal manager As NorthwindEntities) _ As IEnumerable(Of Func(Of INotifyCompleted))
' List of asynchronous functions for the Coroutine to execute serially. Dim funcList = New List(Of Func(Of INotifyCompleted))
' Get all customers Dim loadFnc As Func(Of INotifyCompleted) = Function() Return manager.Customers.ExecuteAsync() ' return an INotifyCompleted End Function
funcList.Add(loadFnc)
' Make a change to them and save them Dim saveFnc As Func(Of INotifyCompleted) = Function() UpdateAllCachedCustomers(manager) ' sync operation to update customers in cache - for example Return manager.SaveChangesAsync() ' return an INotifyCompleted End Function
funcList.Add(saveFnc)
' return the list Return funcList End Function Observations: One batcher function fully defines the list of asynchronous tasks to process. Alternatively, you could build up that list externally and then submit it to the Coroutine class. This example assumes you want to think about the batch as a single, unified process. Nesting the asynchronous function definitions within the batcher makes for easier reading as all steps of the process are in one place. Each asynchronous function returns a single implementer of INotifyCompleted. Within that function, all code except the final line must be synchronous. Do not put two asynchronous methods in the same function. Including synchronous functionality is optional. The last line of the batcher returns the function list it's built up. Call Coroutine.Start The Coroutine.Start method of the DevForce Coroutine class will accept either an iterator or a list of asynchronous functions. The Coroutine works the same in either case, pausing until each asynchronous function completes. C# var op = Coroutine.Start(LoadAllCustomersUpdateAndSaveCore(manager)); op.Completed += (s, e) => { if (e.CompletedSuccessfully) { ShowMessage("All customers were updated"); } else { ShowMessage(e.Error.Message); } }; VB Dim op = Coroutine.Start(LoadAllCustomersUpdateAndSaveCore(manager)) AddHandler op.Completed, Sub(s As Object, e As CoroutineCompletedEventArgs) If e.CompletedSuccessfully Then ShowMessage("All customers were updated") Else ShowMessage(e.Error.Message) End If End Sub Parallel coroutine with function list We can adopt a similar approach to run a collection of asynchronous tasks in parallel. Imagine a batcher called LoadReferenceEntitiesCore that asynchronously loaded sets of reference entities such as colors, states, unit types, etc. You would write this in precisely the same way that you did LoadAllCustomersUpdateAndSaveCore, the only difference would be in the functions that you added to the funcList. We'd call StartParallel instead of Start as in this example, which also uses a callback instead of an event handler. C# Coroutine.StartParallel( LoadAllCustomersUpdateAndSaveCore(manager), // batcher (op) => // callback { if (op.CompletedSuccessfully) { ShowMessage("References entities loaded"); } else { ShowMessage(op.Error.Message); } } ); VB Coroutine.StartParallel( _ LoadReferenceEntitiesCore(manager), _ ' batcher Sub(op As CoroutineOperation) ' callback If op.CompletedSuccessfully Then ShowMessage("Reference entities loaded") Else ShowMessage(op.Error.Message) End If End Sub
Learn about related technologies You may find these resources helpful as you work with other technologies in your DevForce application. Entity Framework Programming Entity Framework, 2nd edition, by Julie Lerman. Absolutely essential, for beginners and experts alike. It's comprehensive and it's big but it's easy to read in small doses. Get the second edition for Entity Framework version 4 published in August, 2010. Julie posts about Entity Framework regularly on her blog. The Microsoft-sponsored videos are short, excellent guides to building entity models in Visual Studio. Microsoft maintains a list of resources. The Microsoft documentation on EDM modeling tools is noteworthy. C# C# 4 in a Nutshell by Albahari brothers. By far the best book for the seasoned programmer and the C# expert who thinks he knows everything. We really like their LINQPad too which is an incredible interpreter for quickly trying out LINQ and C# expressions. LINQ C# 4 in a Nutshell belongs under the C# category but deserves a second mention here for its crisp and clear treatment of the subject. We think it's the best. LINQ in Action for gory details MVVM Model-View-ViewModel (MVVM) is a recommended pattern for building screens or views. It is a pattern for arranging UI code. MVVM is primarily concerned with separating the controls on the glass (the View) from the logic that feeds and responds to those controls (the ViewModel). The ViewModel often manages the flow of data between objects in a Model and their presentation in the View MVVM is not itself a framework nor is there one necessary implementation. In fact, many DevForce examples and reference implementations are done in MVVM style. The MVVM pattern and its implementations have almost nothing to say about the Model. They leave that to you ... or in our case, they leave it to DevForce. DevForce plays well with all kinds of MVVM implementations. Think of DevForce as supplying the Model to your MVVM implementation. Accordingly, you can pick any MVVM style or implementation that works for you... and fit DevForce right in. Silverlight Silverlight 4 Unleashed by Laurent Bugnion (due November 2010). Laurent is the author of the open source MVVM-Lite Toolkit and the author of the earlier edition for Silverlight 2. A top choice. Silverlight 4 in Action by Pete Brown. Excellent content and a pleasure to read. Covers much the same ground as Laurent's book but with a different perspective. Get both books. Data Driven Services with Silverlight 2 by John Papa is dated but much of value remains. Silverlight 4 improves upon but is not radically different than Silverlight 4. The biggest gap is the absence of WCF RIA Services (which did not exist at the time) and DevForce. Azure Azure in Action by Chris Hay and Brian H. Prince. A good choice for learning what Azure is and why it matters as well as how to make it work. Windows Identity Foundation Guide to Claims-Based Identity and Access Control from the Microsoft Patterns and Practices team. Excellent tour of federated identity issues and how WIF addresses them written with a minimum of jargon and a maximum of real world sensibility. Why are we even mentioning WIF? Because more and more of our customers are moving to Azure and the cloud presents special security challenges for which WIF is well suited. Nuts and bolts books will come ... but this will endure simply because it is such a clear and compelling introduction to the space.
Create the entity model Last modified on March 25, 2011 15:39 This topic describes how to create an Entity Data Model (EDM) in Visual Studio using the EDM Designer that has been supplemented by the DevForce EDM Designer Extension. These discussions are DevForce focused and often assume a familiarity with Entity Framework and the tooling that can be learned from other resources. Start with the EDM Wizard Create a new Entity Data Model (EDM) with the Entity Framework's EDM Wizard
DevForce developers begin entity model development by defining an Entity Framework Entity Data Model (EDM). You create a new EDM using the Entity Framework EDM Wizard. In this topic we show most of what you need to use the EDM Wizard but it's also worth looking at Microsoft's documentation in MSDN Pick the model project The first question it "where do I put the model?". You have to pick the project that will be home to your entity class model. The DevForce tutorials teach you to begin with a DevForce n-tier template and add the model to the web application server project. That's a simple way to learn DevForce. Although not ideal in the long term, don't worry about it now; you can break that model into a separate project later if you want to. More experienced DevForce developers create a dedicated model project up front. Make sure you create the model project as a full .NET 4 class library. Either way, you'll add your entity model classes to a full .NET 4 project. Add an ADO Model item Having decided upon the project that will hold your model, do the following: Select the project in the Visual Studio 2010 (VS) "Solution Explorer" window Select "Add a new item" from the context menu (or press Ctrl-Shift-A) Type "ado" in the search TextBox in the upper right of the dialog Select the "ADO.NET Entity Data Model" template Enter a name for the model in the "Name:" box (e.g., DomainModel.edmx) Press Enter You have launched the "Entity Data Model Wizard". Note that the template added the .edmx file extension to your model name; you picked the wrong template if it didn't. The wizard offers two ways forward: 1. "Generate from database" - the Data First approach 2. "Empty model" - the Model First approach
Data or Model First? Do you want to generate the entity model from an existing database? Choose #1 and follow the "Data First" path. Most developers go this way, either because they are committed to a legacy database or because they prefer to define the database and the entity model at the same time. You might prefer #2 - "Model First" - if you are building the application from scratch, have no legacy database to worry about, and want to delay defining a database until you've worked out your entity model. Both approaches produce a Conceptual Entity Model that defines the shapes and relationships among the entities that will eventually become your entity classes. "Data First" produces a full EDM that maps the Conceptual Model to the Storage Model. The Storage Model describes the database schema; the mapping correlates the Conceptual Entities with tables and columns in storage. This full EDM is required by the Entity Framework to read and write to the database. In this topic we consider only the "Data First" and "Model First" approaches. The Entity Framework "Code First" does not use an EDMX at all. Try DevForce POCOs if you prefer "Code First". The "Model First" approach yields an EDMX without mapping and storage descriptions. You can add the Storage Model and Mapping later but until you do you won't be able to access a database with this EDM. DevForce is OK with that. DevForce only needs the Conceptual Model to generate entity classes. You can develop and test your application quite effectively using an offline entity cache or a fake backing store populated with dummy data. When you choose "Empty model", the wizard generates a skeletal EDMX file and sends you straight to the EDM Designer with a blank canvas. The EDM wizard has done its job. You proceed to manually add entities and associations to the Conceptual Model. Create the model from the database In the "Data First" approach, you point the Wizard at an existing database and tell it to generate entities corresponding to selected tables and views. This is by far the most common approach to entity modeling. Identify the database First you must identify the database that will be the schema source. This needn't be the production database; in fact it should not be. Pick a version of the database with a suitable schema; it should be a database you can read to and write from during development. Many developers are comfortable creating and updating a design database using other tools such as SQL Server Management Studio (SMS). They alternate between the EDM Designer and SMS as they evolve the model. The wizard asks you to pick a database connection as in this example:
Your target database will be in the drop-down list if you've previously created a connection to it in Visual Studio. The "Server Explorer" window reveals the connections known to Visual Studio at the moment:
You can remove an unwanted connection from the "Server Explorer" - and from the EDM Wizard connection list - by selecting it and pressing the Delete key. If the target database is not in the list, press the New Connection... button to open the Visual Studio "Connection Properties" dialog. This screenshot shows the default connection in SQL Server for the "NorthwindIB" tutorial database as it was installed with DevForce.
Pick database objects to model The wizard connects to the database you picked and reads the database schema. This can take from seconds to minutes. Eventually it presents an "Choose objects" dialog with a list of database objects categorized by "Tables", "Views", and "Stored Procedures". In the example shown in the following screenshot, the developer is modeling tables:
Some observations: 1. The "Tables" category shows the database tables that have not yet been modeled. This developer picked four tables. 2. The wizard can use English language pluralization rules to generate entity class and property names. When the pluralization option is checked, the wizard creates a Customer entity type mapped to either a "Customer" (singular) or "Customers" (plural) table. The wizard sees that a Customer has multiple related orders so it creates a Customer.Orders (plural) navigation property. The wizard does a decent job of English pluralization; non-English speakers might turn this off. In any case, you can set these names directly in the designer. You can turn pluralization on and off later as well. 3. You must check the "Include foreign key columns" option which tells the EDM designer to create "Foreign Key Associations" and expose foreign key columns as entity properties. DevForce requires this option. 4. The "Model Namespace" value isn't used by DevForce. DevForce generates the entity classes with the project's default namespace ... unless you override that default by setting the "Custom Tool Namespace" property of the DevForce template (the .tt file). When you press Finish, the wizard generates the EDM and hands off to the EDM Designer. Now is a good time to save the model and review the EDM's connection to the database. Refine in the EDM Designer This topic introduces the Entity Data Model (EDM) Designer which is a Visual Studio tool for defining conceptual entities and mapping them to the database.
Introduction The EDM Designer is a Visual Studio design tool for defining your Entity Data Model (EDM). It reads and updates the EDMX file that represents the model in XML. Every DevForce developer who is responsible for writing and maintaining an Entity Framework EDM will use this tool. You can implement almost every EDM feature with the EDM Designer. A few features require editing in raw XML and some tasks can be accomplished more quickly in XML. But the EDM Designer is preferred because the EDMX file is large and challenging to work with directly. Microsoft enumerates in its MSDN documentation on the designer those Entity Framework features that cannot be specified or controlled through the designer. When you have created a new EDM with the EDM Wizard, the wizard leaves you in the EDM Designer. If you chose the "Data First" approach and generated the initial model from a database schema, the EDM Designer might look a bit like this:
The screenshot shows two of the four windows within which you specify the conceptual entities and how they map to the database. You may be completely satisfied with the model as it was initialized from the database schema in which case your next stop might be to review the generated entity classes and start customizing them with your own business logic. Most developers adjust the EDM here in the designer in order to shape the entities to meet their needs. They may want to change the name of an entity (or property) from its default value which is the name of the database table (or column). establish an inheritance hierarchy among some of the entity types. add a custom base class designate concurrency properties specify how keys are generated consolidate related simple properties into a ComplexType property add XML documentation that will appear in the generated classes. The basic workflow for such changes is: Select the object (entity, property, association) to modify in the diagram or search for it in the "Model Browser" window. Set the EDM properties for that object in the "Properties" window. Adjust mappings in he "Mapping Details" window. Save, causing DevForce to re-generate the entity classes. Build the application and test it. Some of the object properties that you set in the designer are actually DevForce-specific properties. They were added by the DevForce EDM Designer Extension when you installed DevForce itself. Update the model from the database You probably shouldn't model every table in the database all at once. You'll add new entities over time and, if you use the Data-First approach, you'll want to initialize those entities and their mappings from information in the database just as you did when you first created the model with the EDM Wizard. When you change the database, you may need to change the EDM to match. Don't start over. Use the Update Model Wizard described here. Learn More The Microsoft documentation in MSDN is a place to start. Microsoft-sponsored Entity Framework videos are short, excellent guides to building entity models with the EDM Designer. Julie Lerman's Programming Entity Framework, 2nd edition is authoritative on Entity Framework matters in general and the EDM Designer in particular. Julie's blog will keep you on top of the latest EF techniques. EDM Designer windows The Visual Studio EDM Designer has four windows which are introduced in this topic.
You'll define and refine your Entity Data Model (EDM) in the EDM Designer. The designer uses four windows to display EDM information and take your input as we see in this screenshot:
The four windows are: Window Summary Canvas A design surface that displays a diagram of the conceptual entities. Model Browser A searchable tree view of EDM objects. Properties The configuration of the currently selected object. Mapping Details The correspondence between a conceptual entity and storage objects. The following sections briefly describe the purpose and capabilities of each window. We recommend that you consult other resources for more detail; Microsoft's own documentation on the designer is a good place to start. Canvas The "canvas" is a visual design surface for editing the conceptual model. You can create, modify, or delete entities and associations by interacting with the diagram graphics.
This is the main work area for many developers. Some of the widely used canvas gestures and actions are discussed in another topic devoted to the subject. Model Browser The "Model Browser" is a tree view of the conceptual and storage models.
This is an alternative means of navigating the model and is explored in greater detail here. Properties Window The "Properties Window" displays details about entities, entity properties, associations, complex types, storage objects and the model itself. This snapshot shows the properties of the Customer's CustomerID key property.
The object properties can be viewed alphabetically or grouped in categories as we see here. The DevForce EDM Extension added a category of DevForce-specific object properties, shown here outlined in red. Learn more about EDM properties, the DevForce properties in particular, in this related topic. Mapping Details The Mapping Details window shows how entities and ComplexTypes are mapped to objects in the storage model. In this screenshot, the developer selected the Customer entity and opened the Mapping Details window. It shows how the Customer entity is mapped to the "Customer" table.
The table columns are outlined in red on the left; the corresponding entity properties are outlined in blue on the right. Although the column and property names are identical in this mapping, you know the columns are on the left because the data types are database data types. The property types on the right are entity data types. The details are editable. This window is used extensively when modeling inheritance relationships and splitting entities. These somewhat advanced techniques are best learned by consulting external resources on Entity Framework modeling. When you select an association and look in the Mapping Details window, it will be empty as seen here.
If must always be empty because DevForce only supports "Foreign Key" associations. If an association appears in this window, it is the wrong kind of association and must be fixed.
EDM Designer canvas The Canvas is the main window in the Visual Studio EDM Designer. It a design surface that displays a diagram of the conceptual entities that you can navigate and edit. This topic describes some of what you are likely to do on the canvas.
The "canvas" is a visual design surface for editing the conceptual model. You can create, modify, or delete entities and associations by interacting with the diagram graphics.
You should consult other resources for more a full understanding of how to work on the canvas; Microsoft's own documentation on the designer is a good place to start. In this topic we mention a few of the most useful gestures and actions, starting with the most important, undo. Multi-level undo Mistakes are easy to make, especially as you try unfamiliar features such as deleting entities and adding ComplexTypes. It's a great comfort to know that the designer supports multi-level undo (Control-Z) and re-do (Control-Y). Actions from the bare canvas Right-mouse-click on any blank spot.
This context menu controls the model as a whole. Add creates a new entity, association, inheritance, or complex type. You're most likely to use this feature in Model-First development; The Data-First developer adds and updates entities via the "Update Model from Database..." option. Diagram, Zoom, Grid, and Scalar Property Format control the appearance of the diagram. Model Browser opens the designer's Model Browser window which is the best way to navigate a large model. Update Model from Database... is how the Data-First developer adds and updates entities based on database schema. It launches the Update Model Wizard. Generate Database from Model.. generates SQL scripts for creation of a database that matches the current conceptual model. This is useful for the Model-First developer; the Data- First developer can ignore it. Properties opens the designer's Properties Window for the model as a whole. Validate the model Validate checks the entire model and reports errors and warnings to the Visual Studio "Error Window". Microsoft documentation lists some of the errors and suggests remedies. It's worth validating the model regularly while working in the designer, especially after updating the model from the database. Errors are usually show-stoppers. You won't be able to generate to use the EDM or generate clean entity classes until you fix them. A few you can safely ignore ... when you understand and accept their causes. In Model-First development you expect to see "Error 11007: Entity type 'SomeEntity' is not mapped" because you are delaying the mapping exercise until later. It won't prevent code generation. Entities & associations This screenshot shows two related entities.
The entity names, Customer and Order, appear in the editable headers at the top of each box. The line between the entity boxes is the association that relates them. The EDM Wizard produced that association based on the foreign key relationship between the matching "Customer" and "Order" tables. The cardinality of the association is denoted in the diagram by the "0..1 - *" text which means that a Customer has zero or more related Orders. The Properties section shows both scalar and complex type properties. A scalar property returns a single value such as a number or a string. Customer.CompanyName is a simple property returning a string. These two entities only have scalar properties. A complex type property combines multiple inter-related properties into a single type. Both Customer and Order might benefit from combining five of their properties into a common Address complex type. Navigation Properties appear at the bottom. A navigation property traverses an association from one entity to another. The Customer.Orders property is a collection navigation property that returns child orders of a parent customer. The Order.Customer property is a reference navigation property that returns the parent customer of a child order. Order has two other navigations to entities not shown. DevForce implements both reference and collection navigation properties as specialized entity queries. You cannot change the display order of the properties in the diagram from within the designer. The display order is determined by the order of the properties in the EDMX file. You can edit the EDMX file and re-arrange the XML property definitions in the CSDL section if the display order matters to you. Renaming You can rename an entity or its property in the object on the diagram.
Entity property context menu Right-mouse-click on a property to get its context menu
Add new properties to the conceptual entity in any of three flavors. You're most likely to use this feature to add a ComplexType property or when developing inModel-First style. The Data-First developer adds and updates entities via the "Update Model from Database..." option. Refactor into New Complex Type after selecting multiple properties to be combined into a ComplexType. Delete removes the property from the entity, leaving the corresponding store column unmapped. You can re-map that column later. The model validates if the store column remains unmapped as long as it is nullable or has a store-determined default value. Validate reports an error otherwise. Entity Key should be checked if the property participates in the EntityKey; CompanyName does not. Table Mapping opens the Mapping Window and displays the mappings for the entity to which this property belongs - Customer in this case. Stored Procedure Mapping opens the Mapping Window and displays the stored procedure mappings for the entity to which this property belongs. DevForce can take advantage of Entity Framework support for entity stored procedures. Show in Model Browser opens the Model Browser window and positions focus on the selected property in the selected entity. The Model Browser is often a more convenient way to get around in a large model. A similar option in the Model Browser takes you back to the property in the diagram. Properties opens the designer's Properties Window for the selected property. Association context menu Right-mouse-click on the association line between two entities to see the context menu
Delete removes the association and simultaneously deletes both of its navigation properties. The model will fail to validate if the storage model mapped that association to a foreign key relationship in the database. Select opens a sub-menu with can take you quickly to either end of the association line. It's enormously helpful when navigating a complex diagram with many long, overlapping association lines. Show in Model Browser opens the Model Browser window and positions focus on the association. The Model Browser is often a more convenient way to get around in a large model. A similar option in the Model Browser takes you back to this association in the diagram. Properties opens the designer's Properties Window for the selected association. Hover over the association line to reveal the tooltip that indicates (a) the cardinality (0..1 to many) and (b) the source / target (aka, principal / dependent roles) involved. Referential Constraint dialog Double-click an association line to reveal the Referential Constraint dialog
Every association in a DevForce EDM must be a "Foreign Key" association with referential constraints defined. Delete entity To delete an unwanted entity, select it in the canvas, open its context menu and pick Delete. The "Delete Unmapped Tables and Views Dialog" appears. The dialog name is misleading. This is the dialog to delete unwanted entities. It can delete the entity's mapped table (or view) in the process.
The dialog offers three choices which Microsoft seems to have documented incorrectly. The empirical behavior is as follows: Yes: Deletes both the selected conceptual model objects and the storage model objects to which they were mapped. It is on this point that reality and the Microsoft documentation differ. No: The selected conceptual model objects will be deleted. "No" means that no storage model objects will be deleted. Cancel: The operation is canceled. No objects are deleted . Yes is the default and is usually what you want. You'd pick No if you wanted to remove the entity but keep the table. That's rare; you might do this when "splitting an entity" among two tables as shown in Matthieu Mezil's video. When you say No, the entities are gone but their tables remains in the Model. Note that EF doesn't like unmapped tables; the model will fail validation. If you decide not to map the table after all, use the Model Browser to drop it from the model. EDM Model Browser
The EDM Designer's Model Browser window presents the conceptual entities and related database storage objects in a searchable tree view. The Model Browser is an easier way to navigate a large model than the EDM Designer canvas window.
The Model Browser is one of the EDM Designer windows. When the EDM gets large, the number of items can overwhelm the EDM Designer canvas. The diagram becomes small and dense and it is too hard to find the entity or association that interests you. The Model Browser affords a more manageable, alphabetically sorted view of the model. Unlike the canvas, it presents both the conceptual entity model and the related database storage model in a tree view. The Model Browser does not display the mappings between the conceptual entities and the storage schema; for that you go to the EDM Mapping Details window. Here's a Northwind-based EDM in the Model Browser with the conceptual entity types unfurled.
The top two nodes, Northwind and Northwind.Store, hold the conceptual model and store model definitions respectively. The properties of the Northwind node define critical code generation characteristics and merit close attention on their own. The balance of this topic takes you on a brief tour of the Model Browser. See the Microsoft documentation for more detail. Search You can search in the Model Browser for an entity, property name, association, complex type, store object ... just about anything ... by entering some text in the search TextBox at the top. We look for "Company" in this screenshot:
Notice it found "Company" in the name of a property of the Customer entity and in the name of a column in the "Customer" table. Notice also the two red stripes on the window's scroll bar, indicating the approximate location of every mention of "Company". That's a valuable clue when trying to find something in a large model. Context Menu Right-mouse-click any object in the Model Browser to get a context menu that is specific to the object type. The entity object context menus look like this one; the store object context menu is shown below.
Show in Designer is a particularly useful option because it takes you directly to the corresponding graphical object in the canvas. That's crucial because some EDM functions - such as delete - are only available on the canvas. Table Mapping opens the Mapping Window and displays the mappings for the selected entity. Stored Procedure Mapping opens the Mapping Window and displays the stored procedure mappings for the selected entity. DevForce can take advantage of Entity Framework support for entity stored procedures. Update Model from Database... is how the Data-First developer adds and updates entities based on database schema. It launches the Update Model Wizard. Generate Database from Model.. generates SQL scripts for creation of a database that matches the current conceptual model. This is useful for the Model-First developer; the Data- First developer can ignore it. Validate checks the entire model and reports errors and warnings to the Visual Studio "Error Window". EDMX validation is covered in another topic. Properties opens the designer's Properties Window for the selected object which could be an entity, association, complex type, storage object, or the model as a whole. Conceptual model object properties Each entity object has its own distinctive properties as depicted in these screenshots. Most of the properties are editable. Their meaning and values are discussed in the Properties Window topic. Model The model properties level, "Northwind" in this example, controls diagram appearance and code generation for the EDM as a whole.
Entity property
Association
Storage objects You can browse the store object to which the entities are mapped.
You can't change anything about the store objects; their properties are read-only. Delete store object
You can drop a store table, view, or stored procedure from the Model Browser by selecting it and picking delete from the item's context menu as seen here.
This little known feature is the only way to drop a table from within the EDM Designer when there is no corresponding, mapped entity. Usually you delete the table when you delete the entity. But you can opt out and just delete the entity; the table definition remains behind, unmapped, in the storage section of the EDM. If you later decide that you don't want to map the table at all, this is the way to get rid of it. You are only deleting the store object from the model, not from the database.
EDM Designer properties The EDM Designer presents a different properties window for each kind of object in the model. This topic describes many of the properties of each conceptual model object types and how to set them.
The EDM Designer Property window lists EDM control properties for EDM objects such as entities, associations, complex types, store objects, and the model itself. Each kind of object has its own list of properties, appropriate to its type. The most important - and the only properties you can change - are the Conceptual Model properties. The DevForce EDM Extension supplements this set with its own properties to guide DevForce generation of entity class code. This topic concentrates almost exclusively on the DevForce designer extension properties. You can learn about the base designer properties from other sources. EDM Property Sets Five property sets correspond to the five kinds of Conceptual Model objects: 1. The Model itself 2. Entity 3. Property 4. Association 5. Complex Type We only consider the first. The Complex Type has a Tag property but is otherwise uninteresting. The Association is a worthy topic of its own but has no DevForce-specific EDM properties. Pin the Properties Window open throughout this exercise and sort by category. Open the Model Browser; it's easier to review a model in the Model Browser than on the design canvas. Model EDM Properties Select the Model-level node which is "Northwind" in this screenshot.
Property Description DataSource Key An arbitrary string name that ties this EDM to a single data source. At runtime, DevForce uses this name to find the appropriate database and its connection string. The code generator adorns each entity class with an DataSourceKey attribute that specifies this name, thus binding the entity class to the model's datasource. DevForce Enabled Whether DevForce should generate code for this model. EntityManager Name Name of the model-specific, EntityManager subclass that is generated for this model; see below. Generate Binding Attributes Whether to generate the binding attributes - BindableAttribute, DisplayAttribute, EditableAttribute, and ReadOnlyAttribute - for each entity property. These attributes can be suppressed at the property level. Generate Developer Classes Whether to generate for each entity an empty partial class file for developer customizations. False by default. Such files are easy to create manually when needed. Injected Base Type Name of the custom base class to inject at the root of the entity model hierarchy; see below. Max. Classes per File An integer specifying the maximum number of entity classes to generate in a single class file; see below. Odata enabled Whether model-specific EntityManager can be used as an OData DataService. When enabled, DevForce adds attributes and code to the EntityManager to support OData and adds OData library references to the project. Tag An arbitrary string of your choosing. Use it to guide your custom code generation. Validation Attribute Mode Whether to use DevForce Verification attributes or the .NET attributes from System.ComponentModel.DataAnnotations when adding validation attributes to entity properties. See the "Validation" topic to assess the merits of each choice. EntityManager subclass DevForce generates a custom EntityManager with model-specific members that derives from the EntityManager. The signature for such an EntityManager looks like this: C# [IbEm.DataSourceKeyName(@"NorthwindIBEntities")] public partial class NorthwindIBEntities : IbEm.EntityManager { ... } VB <IbEm.DataSourceKeyName("NorthwindIBEntities")> Partial Public Class NorthwindIBEntities Inherits IbEm.EntityManager End Class The "EntityManager name" matters, especially in a project with multiple EDMX files. If a single project has multiple EDMX files and all of the models share the same EntityManager Name, then the code generator emits a series of partial class files that compile together to form a single custom EntityManager class that spans those models. If the models each have their own "EntityManager Name", each model gets its own EntityManager subclass. Injected base type DevForce entity class must ultimately inherit from Entity. If the "Injected Base Type" is blank, the entity classes generated from this model will either inherit directly from Entity or from another class in the model. You can insert your own base class between Entity and the other generated entity classes by naming that class in the "Injected Base Type". There are rules about this class: Your base class must inherit from Entity or from another class that inherits from Entity. If the "Injected Base Type" name is a "qualified name" - meaning it contains a period ('.') - then that class is assumed to exist, derives from Entity, and is in a referenced assembly of the model project. If the "Injected Base Type" is not "qualified" - its name does not contain a period - DevForce generates a partial class file for the base class; the generated class inherits from Entity but is otherwise empty. In either case, DevForce generates model entity classes that derive from your base class instead of Entity. The reasoning is as follows. Your base class may reside in another project where it can be shared with other models in other projects or even other applications. Such a class would have its own namespace, hence the period in the class name. On the other hand, you may prefer to define the base class in this project for this model. Generating the base class as a partial class is harmless, guarantees that all model entity classes really do derive from Entity, and ensures that the project will compile ... even if you neglect to fill in the base class details in your companion partial class file. Max. classes per file Models with a large number of entities can sometimes result in generated code files that are too large to be processed by the Visual Studio editor. This problem may be avoided by generating code into more than one file. The Max classes per file setting will limit the number of classes that are generated into a single file and will create as many files as are necessary to meet the specified constraint for each file. The default value for this property is 100 but can be adjusted in either direction. If it is set to 1, then each generated file will contain only a single class. Having many generated files is often a bad idea because of issues involved with synchronizing many changed files within a source control system. We recommend leaving this default as is unless you have a specific issue that requires changing it. Entity EDM Properties Select an Entity node such as Customer as shown in this screenshot.
Property Description Can Query Whether the client is allowed to send a query to the server that returns or involves this type. The choices are True, False, and Default. If True or False, the code generator adds the corresponding ClientCanQueryAttribute to the entity class. Default is equivalent to True and suppresses the attribute altogether. A custom server-side EntityServerQueryInterceptor is the ultimate arbiter of the client's right to query the type. CanSave Whether the client is allowed to save entities of this type. The choices are True, False, and Default. If True or False, the code generator adds the corresponding ClientCanSaveAttribute to the entity class. Default is equivalent to True and suppresses the attribute altogether. A custom server-side EntityServerSaveInterceptor is the ultimate arbiter of the client's right to save instances of this type. Tag An arbitrary string of your choosing. Use it to guide your custom code generation. Query and save authorization example In the following example, the developer specified that the client can query but not save Customers. The developer left both authorization properties as Default for the Employee entity. C# using IbEm = IdeaBlade.EntityModel; ... [IbEm.ClientCanQuery(true)] [IbEm.ClientCanSave(false)] public partial class Customer : IbEm.Entity { ... }
public partial class Employee: IbEm.Entity { ... } VB Imports IbEm = IdeaBlade.EntityModel ... <IbEm.ClientCanQuery(True)> <IbEm.ClientCanSave(False)> Partial Public Class Customer Inherits IbEm.Entity End Class
Partial Public Class Employee Inherits IbEm.Entity End Class EDM Properties for Properties Entity and ComplexType properties are each defined by a set of EDM properties. The EDM properties for the Customer.CompanyName property are below:
Property Description Getter The access mode for the property getter; see note on property accessibility Setter The access mode for the property setter; see note on property accessibility Attribute suppression A collection of settings that determine which of many possible attributes can be generated for this property; see "Attribute suppression" below. Bindable Mode A hint to the UI about whether the property can be bound OneWay, TwoWay, or None=not at all. It's the parameter to the DisplayAttribute which is generated for the property unless suppressed. Concurrency Strategy Whether this property participates in optimistic concurrency conflict detection and if so how; see below Display Name A hint to the UI about what name to use when constructing a label or grid column header for this property. It's the parameter to the BindableAttribute which is generated for the property unless suppressed. Tag An arbitrary string of your choosing. Use it to guide your custom code generation. Attribute suppression By default, the code generator adorns entity entity or complex type property with the attributes that are appropriate for that property. The "appropriate" attribute depends upon characteristics of the property such as whether a setter exists, if the property is nullable or not, if it is a string of known length (see the "Facets" category of EDM properties), etc. Model-level settings can suppress some of the attributes for all properties in the model. You also can suppress attribute generation for each entity and complex type property individually by opening the Attributes to Suppress collection and setting any of them to True. The attributes you can suppress are: Attribute Description Bindable Suggests to the UI how the property should be bound: OneWay, TwoWay, or None=not at all. DefaultValue Specifies the default value to use during new entity initialization. DisplayName Suggests the text that the UI should use to write labels and grid column headers. Editable Suggests to the UI whether it should enable editing of this property. Key Whether this property participates in the EntityKey. Metadata Whether to look at the entity's companion metadata class for the attributes to adorn the property. ReadOnly Whether the property is read-only or read/write. Validation Whether to generate validation attributes. Remember: these setting enable or suppress attribute generation. They do not set the values of the attributes. For example, setting Editable false suppresses the EditableAttribute; it does not disable editability. Finally, for ultimate control over attribute generation, you can write an entity metadata class and set every attribute manually. Concurrency strategy The "Concurrency strategy" choices are described in a topic dedicated to concurrency. The choice you make here appears in the generated property code as one of the parameters to the property's DataEntityProperty constructor, as seen in this example: C# using IbEm = IdeaBlade.EntityModel; ... // RowVersion's DataEntityProperty public static readonly IbEm.DataEntityProperty<Customer, Nullable<int>> RowVersion = new IbEm.DataEntityProperty<Customer, Nullable<int>> ("RowVersion", ..., ..., IbEm.ConcurrencyStrategy.AutoIncrement, ...); VB Imports IbEm = IdeaBlade.EntityModel ... ' RowVersion's DataEntityProperty Public Shared ReadOnly RowVersion As New _ IbEm.DataEntityProperty(Of Customer, Nullable(Of Integer)) ("RowVersion", ... , ..., IbEm.ConcurrencyStrategy.AutoIncrement, ...) Entity property accessibility The property getters and setters are public by default. You can choose the access mode - Public, Internal, Protected, or Private - to control the visibility of the property outside the class. All four choices are available ... except in Silverlight. Silverlight prohibits non-public reflection; that prohibition prevents DevForce from getting and setting entity data while communicating with the server during queries and saves. You can choose Internal, thus limiting access to classed defined within the model project and to assemblies that are friends of the model project. You must also make the project's internals visible to .NET assemblies. Change member visibility You can change visibility of entity classes and properties in the EDM Designer but special rules apply for models used in Silverlight.
Class and property visibility The generated Entity Data Model (EDM) classes and their members are Public by default. That isn't always a good idea. Some classes shouldn't be changed or constructed. Some properties should be read-only. You can change the visibility of a class or property in the EDM Designer. Locate the object in the Model Browser, open its Properties window, and find the "Code Generation" category. Entity and property options differ: Entity visibility
Property getter and setter visibility
You have four visibility choices: Public, Internal, Protected, or Private. You can pick any of them if you don't intend to use the model in Silverlight. Silverlight restrictions In Silverlight your only viable options are Public and Internal. Silverlight prohibits non-public reflection. Silverlight won't let DevForce reflect for Protected and Private types which means DevForce won't be able to get and set entity data while communicating with the server during queries and saves. Pick Internal if you want to hide an entity or property in a model that you will use in Silverlight. Follow the same rule when writing an entity's default constructor. Make project internals visible to DevForce Silverlight normally prevents outside assemblies from accessing internal members. It makes an exception when the assembly's internals are made visible to two .NET assemblies. Add these attributes to the project's AssemblyInfo file. C # // These allow the expression tree built by DevForce for certain complex queries to be processed correctly // in Silverlight. Without it, some complex queries may fail with a MethodAccessException. [assembly: InternalsVisibleTo("System.Core, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d56c76 f9e8649383049f383c44be0ec204181822a6c31cf5eb7ef486944d032188ea1d3920763712ccb12d75f b77e9811149e6148e5d32fbaab37611c1878ddc19e20ef135d0cb2cff2bfec3d115810c3d9069638fe4 be215dbf795861920e5ab6f7db2e2ceef136ac23d5dd2bf031700aec232f6c6b1c785b4305c123b37a b")] [assembly: InternalsVisibleTo("System.Runtime.Serialization, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d56c76 f9e8649383049f383c44be0ec204181822a6c31cf5eb7ef486944d032188ea1d3920763712ccb12d75f b77e9811149e6148e5d32fbaab37611c1878ddc19e20ef135d0cb2cff2bfec3d115810c3d9069638fe4 be215dbf795861920e5ab6f7db2e2ceef136ac23d5dd2bf031700aec232f6c6b1c785b4305c123b37a b")] V B ' These allow the expression tree built by DevForce for certain complex queries to be processed correctly ' in Silverlight. Without it, some complex queries may fail with a MethodAccessException. <Assembly: InternalsVisibleTo("System.Core, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d56c76 f9e8649383049f383c44be0ec204181822a6c31cf5eb7ef486944d032188ea1d3920763712ccb12d75f b77e9811149e6148e5d32fbaab37611c1878ddc19e20ef135d0cb2cff2bfec3d115810c3d9069638fe4 be215dbf795861920e5ab6f7db2e2ceef136ac23d5dd2bf031700aec232f6c6b1c785b4305c123b37a b")> <Assembly: InternalsVisibleTo("System.Runtime.Serialization, PublicKey=00240000048000009400000006020000002400005253413100040000010001008d56c76 f9e8649383049f383c44be0ec204181822a6c31cf5eb7ef486944d032188ea1d3920763712ccb12d75f b77e9811149e6148e5d32fbaab37611c1878ddc19e20ef135d0cb2cff2bfec3d115810c3d9069638fe4 be215dbf795861920e5ab6f7db2e2ceef136ac23d5dd2bf031700aec232f6c6b1c785b4305c123b37a b")> DevForce adds these lines to the project's AssemblyInfo automatically when you first generate your entity model. You may have to add them manually if you moved the model to a different model project. Set property default value You can see and set a property's default value in the EDM Designer. This screen shows that Customer.CompanyName has been given a default value of "-- unknown--".
The EDM Designer adds this default to the definition of the CompanyName property. <Property Name="CompanyName" ... DefaultValue="--unknown--" /> The range of default values is constrained. For example, you can't enter a string that contains characters such as '<' and '>' that are reserved in XML. You can't enter functions either. DevForce code generation adds a corresponding DefaultValueAttribute to the generated CompanyName property. C# [DefaultValue("--unknown--")] public string CompanyName { ... } VB <DefaultValue("--unknown--")> Public Property CompanyName As String ... Finally, DevForce reads the attribute at runtime and records the default in its metadata. The entity creation topic explains how this model default value is used. Combine related values in a ComplexType A ComplexTypeis a class composed of other entity data properties.
The ComplexType A ComplexType is a way to represent complex data within an entity. Consider the Northwind "Employee" table for example. The "Employee" table has FirstName and LastName columns. These become simple data properties of the Employee entity. The "Employee" table also has Address, City, Region, PostalCode and Country columns. In a default mapping, the Employee entity would have five corresponding properties. But conceptually, these five properties are a single thing, an "address". We'd like to treat them as an Address object. We'd like to write anEmployee.Address and expect to get an Address object in return. We'd like to be able to create a new Address object and assign it to anEmployee.Address. We might add validation rules to the Address class that constrain the PostalCode to values that are legitimate for the Region. The Address in this example is a ComplexType and you can create it in the Entity Framework's EDM Designer. Julie Lerman tells how in a blog post and Matthieu Mezil shows how in his video. Here is the Northwind Employee entity with an Address property as seen in the EDM Designer:
It is not an entity A ComplexType is not an entity. It doesn't map to a complete table row. It lacks a unique key. It has no existence independent from the entity to which it belongs. A ComplexType is called a "value type because it is entirely defined by the values it holds, in much the way that the number "5" is a value type. There is no table of numbers and no row representing the value "5". In this example there is no "Address" table either. ComplexType reuse As it happens, those same five address columns appear in two other Northwind database tables: "Customer" and "Order". You can re-map their address columns to the Address type as seen in this screenshot:
ComplexType classes DevForce generates ComplexType classes into your code file as it does the entity classes. The properties of the generated ComplexType classes are similar to generated entity properties and they support notification, validation, and property interception as their entity cousins do. The generated ComplexType classes are partial so you can extend them with custom logic as you do your entity classes. An instance has a parent entity A ComplexType instance doesn't stand alone; it belongs to its parent entity. For example, the Address of the "Nancy Davolio" Employee belongs to the "Nancy Davolio" Employee entity. In DevForce , you can always ask a ComplexType for its parent. C# Assert.AreSame(nancy, nancy.Address.ComplexAspect.ParentEntity);
VB Assert.AreSame(nancy, nancy.Address.ComplexAspect.ParentEntity) This "belonging" is a necessary consequence of a ComplexType. The City, Region, and other properties of "Nancy's" address actually map to columns on the "Nancy" record in the Employee table. There is no Address table in this case; only a subset of the Person table's columns that we want to treat as an address. It follows also that a data property that returns a ComplexType cannot be null. It's member properties can be null - nancy.Address.City may be null. But the ComplexType data property itself - nancy.Address - cannot be null. Learn more Learn more about how to create and work with ComplexTypes in the EDM designer. Advanced modeling topics DevForce supports more than the simple mapping of tables to entities we've already discussed. Here we'll briefly discuss more advanced modeling topics using the Entity Data Model (EDM) designer.
Data Providers DevForce works with any of the ADO.NET data providers which support the Entity Framework. You can find more information here. Inheritance DevForce supports all the forms of inheritance, table mapping and splitting that the Entity Framework supports. Microsoft walkthrough on Table per Hierarchy inheritance mapping Microsoft walkthrough on Table per Type inheritance mapping Microsoft walthrough on mapping an entity to multiple tables Note that there may be performance implications with some of these techniques. Stored procedures DevForce supports stored procedures for both query and create, update and delete actions. How to import a stored procedure for query How to map insert, update and delete functions to stored procedures Code sample on stored procedures Stored procedure queries in DevForce Complex types We've discussed complex types separately in some detail, but here's additional information. How to create a Complex Type Many-to-many DevForce supports many-to-many relationships in the same way as the Entity Framework. If the join table in the database contains no extra payload then the EDM Designer defines only an association in place of a "join entity". If the table contains payload (i.e., additional properties other than the foreign keys) then an entity is created for the table. Many-to-many relationships in EF Adding and removing with many-to-many navigation properties Code sample on many-to-many relationships Cascading deletes DevForce defines the same rules for cascading a delete on a relationship as the Entity Framework does. When you want to automatically delete all of the child records of a parent when the parent record is deleted you can specify the cascade delete rule. As with EF, it is strongly recommended that you specify the cascade delete rules in both the conceptual model and the database. The reason for this is that DevForce will delete only the related objects currently loaded into the entity cache. Handle cascaded deletes when saving Code sample on cascading deletes Cascading deletes in EF Concurrency DevForce supports optimistic concurrency via the Entity Framework. The EF allows you to define one or more concurrency properties for your entities; DevForce extends this support by also allowing you to define a concurrency strategy for each. Managing concurrency during a save Code sample on handling concurrency conflicts Learn more Learn more about the EDM wizard and designer. Descriptions and walkthroughs on mapping and modeling tasks. Model First in the EDM Designer This topic introduces techniques for defining a Conceptual Entity Model in the Model First style using the Visual Studio EDM Designer.
You can define a Conceptual Model for your Entity Data Model (EDM) without referring to a database using the "Model First" approach. DevForce can generate entity classes from the Conceptual Model alone. You can develop and test the application without a database using an offline entity cache or a fake backing store populated with dummy data. Add entities and associations You begin "Model First" development with the EDM Wizard, choosing the "Empty model" option. Then you add entities and relationships directly to the designer canvas. The details are out-of- scope for DevForce documentation. The techniques involved are covered in the standard Entity Framework literature. You might start with these two resources and search the web for more: Model-First in the Entity Framework 4 Creating an Entity Data Model the Model-First Way You work more extensively with the Properties window in "Model First" then you do in "Data First". In "Data First" most of the properties are filled-in for you based on database schema information and there is rarely cause to change those values. In "Model First" there is no database schema; you specify the details of every property and association according to your plan for the entity. Foreign Key Associations only You must create "Foreign Key Associations" when adding associations to your model. "Data First" developers don't worry about this because the EDM Designer, once properly configured, ensures that all associations added from the database are "Foreign Key" associations. A "Model First" developer must be alert to the issue potential for the EDM Designer to create an "Independent" association unexpectedly. The designer creates an "Independent" association if you uncheck the "Add foreign key properties..." CheckBox in the "Add Association" dialog, something you might do if the foreign key property happened to be already defined. You must convert an "Independent" association to a "Foreign Key" association by adding a "Referential Constraint". Mapping to a Database When you are ready to associate the application with a real database, you map the Conceptual Model to that database. Right-mouse-click on the canvas after which you select "Generate Database from Model...". The resources cited above walk you through the process. The EDM Designer doesn't actually generate the database. It produces a "DDL script" (Data Definition script) that can generate the database. You can play this script from within Visual Studio or run it separately. Beware! the generated DDL script deletes all objects from the existing database; all of the database data will be lost. If you are using "Model First" to add new entities and tables to an existing database and you want to use the DDL script, be sure to edit it carefully to ensure it does no harm. Above all else, make sure you backup the database before running any scripts! Database generation works well in the beginning when you have don't have a real database with real data and are less inclined to care about an optimal database structure. As the application matures you won't want to use that DDL script. You are more likely to create the table in the database in your own way with your own script even if you've separately designed the corresponding entity in your EDM in "Model First" style. You can still proceed "Model First". You "reconcile" with the database in a different manner. You don't generate DDL Script. You "Update Model from Database..." instead and add the new tables that correspond to the new entities as you would for "Data First". The designer won't recognize the correspondence with your "Model First" entities. if you had created a Customer entity in "Model First" and subsequently added a Customer table to your database, the EDM Designer will add that table as Customer1. Use this behavior to your advantage. Compare Customer and Customer1. When you are convinced that the generated Customer1 is correct, (1) delete your "Model First" Customer entity and (2) rename Customer1 to Customer. We suggest you proceed deliberately, adding only one or two tables at a time. DevForce EDM Designer extension The EDM Designer only collects the information it needs for Entity Framework's native code generator. DevForce needs more information in order to generate its own entity classes. The DevForce EDM Designer extension adds features to the base EDM Designer that help the developer provide that extra information.
Extension installed From the Visual Studio menu pick Tools | Extension Manager ... and scroll the Extension Manager dialog until you see:
The DevForce EDM Designer extension was installed with DevForce. If you don't see this extension, DevForce isn't installed or the extension was uninstalled. In the latter case, you can reinstall it. Shut down Visual Studio and, from the Windows Start menu, pick All Programs | DevForce 2010 v6... | Tools | DevForce EDM Extension. The extension changes the EDM Designer in two ways: 1. It adds new DevForce properties to the Properties panel. 2. It writes the property values into the conceptual model section of EDMX file as custom annotations. Annotations are the approved way to extend the CSDL according to the specification. "Annotations allow applications to embed application-specific or vendor-specific information into CSDL. The format does not specify how to process these custom-defined structures or how to distinguish structures from multiple vendors or layers. Parsers of the CSDL MAY ignore Annotations that are not expected or not understood." DevForce extension properties This screenshot shows the EDM Designer displaying information about an entity property.
The CompanyName property of the Customer entity has been selected in the diagram on the left. The Properties window on the right displays the EDM object definition properties of that CompanyName property, sorted by category. In the middle of the Properties window is a new category, outlined in yellow, labeled DevForce Code Generation. This category was added by the DevForce EDM Extension. The properties in this category govern code generation of the CompanyName property. The Display Name property is outlined in red with a value of Company Name. The developer is telling DevForce to add a .NET DisplayAttribute to the Customers CompanyName property when it generates the entity class code. The DisplayAttribute is a standard .NET UI hint; a UI control could discover that attribute and create a label with the words Company Name next to a TextBox. Many developers like to decorate their entities with such UI hints. The DevForce EDM Extension writes this advice as an annotation to the CSDL section in the EDMX file. The CSDL definition of the CompanyName property looks like this: XML <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" ib10:DisplayName="Company Name" /> The attribute ib10:DisplayName is the DevForce annotation. After code generation the property looks like something like this: C# [Display(Name="Company Name", AutoGenerateField=true)] public string CompanyName {...} VB <Display(Name:="Company Name", AutoGenerateField:=True)> Public ReadOnly Property CompanyName() As String ... End Property Controlling code generation DevForce Code Generation sections adorn every level of the CSDL specification. At the top level are properties to control code generation for the model as a whole:
You can even turn DevForce code generation off and restore the Entity Framework default generator.
Keep the Enabled setting on True and see what DevForce generates. Enable / Disable the extension You can enable or disable the extension itself from within the Visual Studio Extension Manager. Disabling the extension both removes the DevForce properties from the designer and disables DevForce code generation.
EDMX in Source Control Last modified on April 15, 2011 12:11 The EDMX file is a precious asset that defines your Entity Data Model (EDM) and is the source for generating your entity classes. Of course you are using a Source Control System to safeguard your application assets. The EDMX file is one of the most important application assets; please put it in source control immediately. One less obvious but critical point: only one person at a time can work on a given EDMX file because it is almost impossible to merge changes from two versions of an EDMX file. If two people work on the same EDMX at the same time and try to commit their changes, the source control merge tool could corrupt the EDMX as it tries to merge their respective changes. It's no big deal when this happens with code files. Firstly, merging code files usually works; it doesn't work with the EDMX ... just as it doesn't work for Web.config and App.config files. Second, humans can read a code file and resolve conflicting text lines. A human wrote that code and can understand what it should look like. XML files such as the EDMX tend to be written by a program such as the EDM Designer. They are much more difficult to read, understand, and repair. Please treat the EDMX file as a binary file. Your source control system has a way of distinguishing binary files from text files. Find out how to configure it so that an EDMX is treated as a binary file. Review the connection string A "Data First" Entity Data Model (EDM) is related to a database by an Entity Framework connection string. This topic shows you how to find and review the Entity Framework connection string.
Visible in the designer The EDM Designer shows the Entity Framework connection string in the "Properties" window:
Hover the mouse over the "Connection string" value and it appears as a tool-tip. Note the "Metadata Artifact Processing" value indicates that the CSDL, SSDL, and MSL files are embedded as resources in the project assembly. Stored in a config file The connection string is not stored in the EDMX. It is held in a .NET configuration file. The Entity Framework designer writes the connection string to the Web.config if model is created in the web application project; it creates and writes the string to an App.config if the model is created in its own project. When deploying as a web application (e.g., as a Silverlight application), you must copy the <connectionStrings> section from the App.config to the Web.config. The XML for the connection string is the same in either file and might look something like this: XML <connectionStrings> <add name="NorthwindIBEntities"
providerName="System.Data.EntityClient" /> </connectionStrings> The entire string must appear on a single long line. It's been wrapped on this page for legibility. The config XML <connectionString> has three parts: the name of the string within the configuration file the Entity Framework connection string the Entity Framework provider that interprets the string. The name of this connection string, "NorthwindIBEntities", matches the DevForce data source key name for the model. DevForce writes this data source key name into every entity class generated from the model. That's how the DevForce runtime knows which database holds entities of this type. The inner Entity Framework connection string has parts of its own: The location of the metadata artifact files The database provider to use for persistence operations (here it is SQL Server) The database connection string (a string within a string) The "metadata" fragment tells us the names of the metadata artifact files. The "res://*/" segment means these files are embedded as resources in the model assembly; there would be a file path name if the artifact files were loose in the output folder. The root name, "DomainModel" in this example, must match the EDMX file name. If you rename the EDMX file, be sure to update the names of the metadata artifact files in this string. The Entity Framework won't be able to find the artifact files if the connection string has the old names. Update Model from Database When the database changes, your Entity Data Model (EDM) must change to match. This topic shows how to update the model from the database using the Entity Framework Update Model Wizard.
You probably shouldn't model every table in the database all at once. You'll add new entities over time and, if you use the Data-First approach, you'll want to initialize those entities and their mappings from information in the database just as you did when you first created the model with the EDM Wizard. The application is not static. New requirements arrive all the time and they often involve changes to the database some of which you must incorporate into your model. Please don't start over. That can be tempting. But if you do, you'll lose all of the changes you made along the way. These changes are in your EDMX file and there is no easy way to find and merge them into a new EDM. Instead you use the Entity Framework Update Model Wizard to add new entities to your model and to update previously mapped entities when their corresponding database objects have changed. Here we only touch upon the issues that are of most concern to the DevForce EDM developer. You can learn more about the Update Model Wizard from Microsoft's documentation on MSDN. Using the Wizard You can update your model anytime. Right-click on the canvas and select Update Model from Database.... This launches the Entity Framework Update Model Wizard as seen here. Always, always, always backup the EDMX before you update the model from the database.
1. Select the tables to model as entities. The wizard only shows tables (and views) that are not already modeled in your EDM. You can only model a database object once. 2. Keep Pluralize or singularize generated object names checked if you want to continue using the English language pluralization rules; these are the same rules used by the EDM Wizard. 3. Keep Include foreign key columns in the model checked. All associations in a DevForce EDM must be "Foreign Key Associations". When the wizard closes: New entities appear for the tables and views that you added. New properties appear for columns added to tables that are already entities in your model. New properties appear if columns were renamed. The entity property mapped to the old column remains in the model Beware of delete and rename The Update Model Wizard never deletes and never renames anything in your conceptual entity model. The wizard only adds new items and removes mappings that it no longer understands. If you drop the "Customer" table from the database and it was formerly mapped to the Customer entity, the Customer entity remains in your model ... where it is now unmapped because the wizard can't find the matching database table. If you renamed the "Customer" table to "Company", the "Company" table appears in the list of objects you can model. The wizard doesn't know that you already modeled that table under its old name. When you select the "Company" table in the wizard, the wizard adds a new Company entity to your model. It also preserves the Customer entity which is unmapped because the wizard can't find the matching table. The same dynamic applies to properties. If the "CompanyName" column was deleted, Customer.CompanyName remains although it is no longer mapped. If the column was renamed to "CustomerName", there will be a new property, Customer.CustomerNamethat is mapped to that column. The old Customer.CompanyNamesurvives as an unmapped property as well; you'll have to delete it manually. Be very careful when deleting or renaming primary and foreign keys columns. The wizard doesn't understand what you are doing. Existing properties and associations will become unmapped and new key properties will appear. An entity that once had a single part key will have a composite two part key: the old key property under the old name and the new key property under the new name property. This change cascades to the associations that depend upon that key. You will have to make manual repairs when you delete or rename anything in the database. The model will not validate until you repair the model and the entity classes generated from the model will be incorrect until you do. Pay attention to the EDM error messages. They can be a little confusing. You can power through it if you know what changed in the database and remember that the wizard only adds, never deletes. We recommend that you modify the database incrementally, making a small change and then updating the model immediately. This is particularly important when renaming or deleting database objects that you have previously mapped to your EDM. Storage and mapping sections are regenerated The wizard's guiding principal is that the conceptual entity model is yours. That's why it only adds to the conceptual entity model; it won't remove or change anything in the conceptual model. However, the wizard acts as if it owns the storage and mapping schemas. You should assume that the wizard rewrites everything in the mapping and storage schemas. Of course it reads your previous mappings and preserves them if it understands them. But if it erases anything it doesn't recognize or that conflicts with its reading of the database schema. You cannot control this behavior. You cannot tell the wizard "refresh these entities but leave those alone." The wizard reevaluates your entire model ... and rewrites the storage and mapping sections as it sees fit. This fact is especially relevant if you have made manual changes to the storage or mapping sections of the XML. Pay particular attention to the section "Entity Framework Features Not Supported by the Entity Designer" in the Microsoft documentation of the EDM Designer. You can work around those limitations by editing the XML directly to implement features the designer doesn't support. Just remember that the wizard probably will erase those changes. You will have to restore them from your backup of the edmx file. Edit the EDMX as XML This topic shows how to open and edit the Entity Data Model's EDMX file in the Visual Studio XML editor and describes its basic structure.
The Entity Framework EDM Designer is sufficient for most of your Entity Data Model (EDM) development work. The design tool can't do everything. Some times you have to edit the XML file - the EDMX - in a text editor either because the design tool doesn't support a feature or because it is more efficient to perform a repetitive editing chore in the raw XML. Open the EDMX in Visual Studio A search of the web may be the best way to learn how to edit and understand the EDMX XML. The MSDN documentation is a good place to start. Here are brief instructions and an overview: Select the EDMX file in the "Solutions Explorer" window Pick "Open With..." from the context menu Open with the "XML (Text) Editor" The EDMX XML appears. Even a small model can produce thousands of lines of XML. It's actually semi-readable and navigable when you know the basics. To get a handle on it, use the "Outlining" feature to collapse the sections. Pick from Edit | Outlining | Toggle All Outlining from the menu or use the keyboard shortcut, Ctrl-M, L. Unfold the first level to reveal the "Runtime" and "Designer" sections. Unfold the "Runtime" section to reveal the "SSDL", "CSDL", and "C-S" sections
EDM sections The four major sections of an EDMX are now neatly arranged before you. Only the first three, the ones grouped under "Runtime", pertain to the Entity Data Model: SSDL Storage Schema Definition describes the database schema (tables and columns). CSDL Conceptual Schema Definition describes entities (types and properties). C-S Conceptual-Storage Mapping maps conceptual entities to storage objects. The storage schema describes the objects in the database. The storage model XML language is database-neutral; it could describe a Microsoft SQL Server schema or MySQL schema or Oracle schema. The Entity Framework is open to many database products any database product for which there is an EF Data Provider. Most popular relational database products have an EF Data Provider. Of course EF provides its own SQL Server Data Provider out-of- the-box; you can search the web for other providers for other databases. The conceptual schema defines a Conceptual Entity Model in terms of entity types and their properties. The conceptual model XML language is implementation-neutral. It doesnt prescribe a particular representation in code. A reporting engine might use it to create reports. WCF Data Services uses it to produce XML representations and JSON objects to OData consumers. Anyone can read the conceptual schema and interpret it. The typical Conceptual Model interpreter is a tool that generates .NET entity classes. The Entity Framework ships with a primary code generator that runs automatically every time you save changes to the EDM. You can replace that default generator with your own code generator and DevForce does just that. Metadata Artifact Files Entity Framework takes the three EDM sections of the EDMX - the conceptual model, storage model, and mapping XML - and splits them out into separate files. These are the Metadata Artifact Files. The "Metadata Artifact Processing" property, visible in the EDM Designer properties window, determines how these files are deployed; they are either embedded in the model assembly as resources (the default) or copied to the output directory as loose files.
We see in this example (using a reflection tool) that the artifact files are embedded:
The artifact files are easier to manage when embedded so keep them that way unless you have a compelling reason to do otherwise. Designer section The fourth, Designer section tells the EDM Designer how to draw the diagram on the design canvas. It also specifies values for some of the options that influence the behavior of the designer. Here's a screen shot:
You are unlikely to edit the diagram in XML. If you've made a mess of your model diagram or your source control system corrupts it during a merge (it happens), you can erase the contents of the <Diagrams> section; the EDM Designer will recover and create a new diagram. The options are another matter. Most of them can be modified within the EDM Designer. The IncludeForeignKeysInModel cannot be set in the designer and you may need to add that property manually to restore Foreign Key Associations as the default association creation strategy. Foreign Key Associations required Entity Framework v.4 supports two kinds of associations: "Independent" and "Foreign Key". All associations in a DevForce model must be "Foreign Key Associations".
Foreign Key Associations only The Entity Framework v.4 supports two types of association, "Foreign Key Associations" and "Independent Associations". "Independent" associations were the only type of association in version 1 of EF. "Independent" associations hide their foreign keys and they lack a "Referential Constraint" section in their XML definitions. Here's an example: XML <Association Name="CustomerOrder"> <End Type="DomainModel.Customer" Role="Customer" Multiplicity="1" /> <End Type="DomainModel.Order" Role="Order" Multiplicity="*" /> </Association> DevForce cannot work with these key-less "Independent" associations. All associations must be the "Foreign Key" type that was introduced in EF version 4. "Foreign Key" associations expose their foreign keys and have a "Referential Constraint" section as seen in this EDMX fragment. XML <Association Name="FK_Order_Customer"> <End Type="DomainModel.Customer" Role="Customer" Multiplicity="0..1" /> <End Type="DomainModel.Order" Role="Order" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="Customer"> <PropertyRef Name="CustomerID" /> </Principal> <Dependent Role="Order"> <PropertyRef Name="CustomerID" /> </Dependent> </ReferentialConstraint> <Association> Check "Include foreign key columns" in the EDM Wizard When you first created your EDM with the EDM Wizard and chose the "Data First" modeling approach, you were required to check the CheckBox next to "Include foreign key columns". "Include foreign key columns" is especially critical. If you uncheck the option by accident, the "Update Model from Database" wizard creates new associations as "Independent" associations. You have to fix that. You must restore the "Foreign Key" option. You can turn it back on in the "Update Model from Database..." wizard while adding a new object to the model Or you can add the option in XML by editing the EDMX. Open EDMX file in the "XML (Text) Editor" Locate the EF Designer section (it's the last section) Locate the "<Options>" subsection (near the top of the EF Designer section) Add the "IncludeForeignKeysInModel" designer property as seen here XML <Options> <DesignerInfoPropertySet> <DesignerProperty Name="ValidateOnBuild" Value="true" /> <DesignerProperty Name="EnablePluralization" Value="True" /> <DesignerProperty Name="IncludeForeignKeysInModel" Value="True" /> </DesignerInfoPropertySet> </Options> Save and close the EDMX editor You have to manually fix all associations that you created before re-engaging "Foreign Key Associations" as discussed in the "Model First" topic. Converting to a Foreign Key Association You must convert all existing "Independent" association to a "Foreign Key" association by (a) adding the foreign key properties manually and (b) adding a "Referential Constraint". Open the "Referential Constraint" dialog either by double-clicking the Association line in the diagram or pressing the button in the "Referential Constraint" property.
Then fill in the blanks as in this example:
Generate model code Generate entity class code from an Entity Data Model (EDM) prepared with the Entity Framework EDM Designer.
Model project changes Add an Entity Framework Entity Data Model (EDM) to a project and examine the project in the Visual Studio "Solution Project" window. Examples in this topic are based on a separate model project rather than the web application project seen in many tutorials. The model project is free of unrelated artifacts which makes it easier to focus on specifics of the model. The principles are the same for either kind of project.
The EDM Wizard and DevForce code generation have made significant changes to the project: 1. The EDM Wizard added Entity Framework assembly references; DevForce added its assembly references. 2. The EDM Wizard added an Entity Framework connection string to the App.config or to the Web.config if had been a web application project. 3. The EDM Wizard added the EDMX file of XML that describes your model. You fleshed out that model with the EDM Designer. 4. DevForce added the code generation template file with the .tt extension. 5. DevForce generated the nested source code file with the ".Designer.cs". DevForce used the T4 code generator built into Visual Studio to create the source code file. Source code files are regenerated automatically every time you save a change to the EDMX file. You can also regenerate manually by selecting the .tt template file and choosing Run Custom Tool from the context menu. DevForce code generation template The T4 code generator is directed by the DevForce code generation template which reads the EDMX and emits entity model source code file(s). The template file name shares the same root name as the EDMX file, SimpleNorthwind in this example. Entity Framework (EF) has its own T4 template (several templates actually). The DevForce template replaces EF's template when DevForce tooling is installed and enabled. You can restore EF code generation, perhaps to see how the code files differ, but your application probably won't work until you re-enable DevForce code generation. The DevForce template only needs the CSDL section of the EDMX file because the .NET entity classes are entirely described by the conceptual entity model. That's why a DevForce entity class model can be designed in the Model First style which only has a CSDL. Generated source code files There is only one generated source code file in this small example, SimpleNorthwind.IB.Designer.cs. There could be several generated source code files if the model were unusually large. You control the number of classes generated per file - and hence the number of files - by setting the "Max. Classes per File" model property in the EDM Designer. Inside a source code file Never modify a generated source code file by hand. When the file is regenerated as it inevitably will be all of your changes will be lost. There are other ways to customize the generated source code. The following snapshot of a generated class file shows three kinds of classes.
At the top is a model-specific EntityManager component. The EntityManager is the most important component in all of DevForce and you will get to know it well. Below that are the generated entity classes, one after another, each one generated from the conceptual entity defined in your EDM. DevForce generates true Entity Framework classes. You can perform pure Entity Framework operations with these class using EF's ObjectContext in textbook fashion. But the DevForce entity classes differ significantly in their support for distributed application scenarios, validation, property interception, and UI data binding. The Customer class is typical:
Finally, at the bottom, is the EntityRelations class C# /// <summary> /// A generated class that returns the relations between entities in this model. /// </summary> public partial class EntityRelations : IbEm.IEntityRelations { ... VB ''' <summary> ''' A generated class that returns the relations between entities in this model. ''' </summary> Partial Public Class EntityRelations Inherits IbEm.IEntityRelations ... EntityRelations defines every relationship between every entity in the model. Customize the code generation template In general you customize the generated classes by adding more code to the model. You can trim back some of the emitted code by adjusting the generator control properties within the EDM Designer. You can replace all of the attributes with an entity metadata class. If these paths prove insufficient, you can customize the code generation template yourself. Generated EntityManager The DevForce code generator creates a model-specific subclass of the DevForce EntityManager that is specific to the application's entity model and therefore easier to work with. You'll find it at the top of the generated class file. It might look something like this C# /// <summary> /// The domain-specific EntityManager for your domain model. /// </summary> [IbEm.DataSourceKeyName(@"NorthwindEntities")] public partial class NorthwindManager : IbEm.EntityManager { ... VB ''' <summary> ''' The domain-specific EntityManager for your domain model. ''' </summary> <IbEm.DataSourceKeyName(@"NorthwindEntities")> Partial Public Class NorthwindManager Inherits IbEm.EntityManager ... The EntityManager is the most important component in all of DevForce and you will get to know it well. It is the portal to the server, the vehicle for queries and saves, and manages the container of entities (the entity cache) that reside in application memory. It is not part of the model. It is machinery that uses the model but it is not of the model itself. You really dont need a custom subclass of the EntityManager - not the way you need the entity classes. You can do without it. You can use the base EntityManager class for everything and sometimes thats exactly what youll want to do. DevForce generates one anyway because it is often more convenient to work with a custom EntityManager that is enriched with dedicated model properties than to use the base EntityManager.
The collection of custom IdeaBlade.EntityModel.EntityQuery<T> properties is a case in point. You can write a query to retrieve all Customers using an instance of any EntityManager like this: C# query = manager.GetQuery<Customer>(); // a Customer query for any EntityManager VB query = manager.GetQuery(Of Customer)() ' a Customer query for any EntityManager The same query is easier to read and write when we use an instance of NorthwindManager, the custom EntityManager tailored to this specific model: C# query = manager.Customers; // the Customer query predefined for this model VB query = manager.Customers ' the Customer query predefined for this model Finally, notice that NorthwindManager is a partial class that you can extend to suit your needs. Generated entity classes Aside from the custom EntityManager, the rest of the generated source code file concerns the entity model and all most all of the file consists of entity class definitions. This topic offers an orientation to the code generated for the Customer class shown in this screenshot.
Attributes The attributes leap out at you. Those familiar with Window Communication Foundation (WCF) will recognize DataContract and KnownType, attributes that guide serialization. DevForce-generated entities are ready for serialization and not just by DevForce itself as it shuttles entities between server and client. You can serialize entities in your own way for your own purposes as well (e.g., to a file for safe keeping). The DataSourceKeyName is a DevForce attribute that identifies symbolically the data source where the Customer entity data are stored. IbEm is an alias for the DevForce IdeaBlade.EntityModel namespace. Namespaces appear repeatedly throughout the file; the alias helps reduce the file size. Why would we mark each entity class with its data source? Why not denote the data source at the model level? Because DevForce can mix entities from different data sources in the same container. We can mix entities from Database A and entities from Database B in the same EntityManager cache. We can save changes to those entities too; DevForce will arrange a distributed transactional save on the server. DevForce Entity base class The Customer entity inherits from the DevForce Entity class: C# public partial class Customer : IbEm.Entity {...} VB Partial Public Class Customer Inherits IbEm.Entity ... End Class All generated entity classes inherit (ultimately) from the Entity class whose primary functions concern change tracking and access of entity state. The Entity base class is no serious threat to "testability" or "extensibility". You can "new" a Customer in test environments without creating or mocking a dependency. Entity itself does not have any persistence functions. It doesnt query or save. It is persistence ignorant in this strict sense. It may hold a reference to the EntityManager to which it is attached (if, in fact, it is attached); you can "new" a fully functioning and self-sufficient test EntityManager/ in a single line. True, your Customer is DevForce framework aware and your model project must refer to DevForce libraries. You need those references for a functioning system in any case. You also can inject your own custom entity base class into the entity model inheritance hierarchy Data properties The properties defined within the Properties region provide access to persistent data. As such they are often called data properties to distinguish them from the other inhabitants of the property zoo. Data properties are the .NET manifestations of the entity properties described in the CSDL section of the entity data model (EDM). Each data property returns a single instance of a type, usually a simple .NET type such as string or int. A property that returns a simple .NET type can return null if the property is nullable. It could return a ComplexType - an encapsulation of multiple data values in a single type; ComplexType data properties never return a null value. All data properties in this example return simple .NET types as illustrated by the Customer.CompanyName property: C# [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="Company Name", AutoGenerateField=true)] [IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } VB <Bindable(True, BindingDirection.TwoWay), Editable(True), _ Display(Name:="Company Name", AutoGenerateField:=True), _ IbVal.StringLengthVerifier(MaxValue:=40, IsRequired:=True, _ ErrorMessageResourceName:="Customer_CompanyName"), DataMember> Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property The profusion of attributes may seem disturbing. There are more attributes adorning the property than there are lines of code within it! The attributes help the entity fulfill the multiple roles we expect it to play on both client and server. Bindable, Editable, and Display are hints to UI tooling that affect how user controls are chosen and configured. The StringLengthVerifier is a validation rule derived from constraints expressed in the CSDL. DataMember is WCF markup indicating that the value of this property should be serialized. You can alter or remove all of the attributes except DataMember by suppressing them in the EDM designer and by adding alternative annotations in a metadata buddy class. The property is public by default. You can make it less accessible in the EDM designer. Caution: properties of entities used in Silverlight must be public or internal. Because Silverlight prohibits access to protected and private properties, DevForce couldnt read or write the property when serializing to a Silverlight client. The get and set implementations are one-liners that delegate to a member of the entitys inner PropertyMetadata class. The PropertyMetadata class is an advanced topic beyond the scope of this overview. But we can infer some of its capabilities by looking at how it is called within a property definition. C# get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } VB Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set PropertyMetadata.CompanyName.SetValue(Me, value) End Set The property is relying on helper methods to perform functions that go beyond simple value access. In fact, each helper method establishes a property value pipeline one going in and one going out that can do more than access a backing field. The setters in-bound pipeline can intercept values, validate them, and raise events such as PropertyChanged. The getters out-bound pipeline allows the developer to intercept values on their way out. Property interception, validation, and change notification are vital DevForce features that grant the developer fine-grained control over the behavior of an entity at the property level. The simple, one-line syntax conceals the potential for extending the property behavior with custom business logic logic that might be part of the class definition or might be injected dynamically from sources outside the class. Navigation properties A navigation property is a means to retrieve an entity (or a list of entities) to which this entity is related by an association. The association was defined in the Entity Data Model (EDM). In this example, the Customer has an Orders property that returns a list of Orders. For a given customer, the Orders property returns the order placed with that customer. When we exercise the Orders property we say we navigate from the customer to its orders. Here is its implementation: C# [Bindable(false)] [Display(Name="Orders", AutoGenerateField=false)] [DataMember] [IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole1)] public IbEm.RelatedEntityList<Order> Orders { get { return PropertyMetadata.Orders.GetValue(this); } } VB <Bindable(False), Display(Name:="Orders", AutoGenerateField:=False), _ DataMember, IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole1)> Public ReadOnly Property Orders() As IbEm.RelatedEntityList(Of Order) Get Return PropertyMetadata.Orders.GetValue(Me) End Get End Property We have attributes as we did for the data properties. One attribute in particular identifies both the association (called a relation in DevForce) and the direction of the association. That tells DevForce that you are navigating from the Customer to the Orders. More on Associations in a moment. The Orders navigation property is significantly different from the CompanyName data property we reviewed earlier. Orders returns a list of entities, not a data value. The entities could come from the local cache or the get could trigger so-called lazy loading of orders from the server; whether the property does or does not lazy load is dynamically configurable. A lazy load operation is performed asynchronously in Silverlight. The property immediately returns a list of whatever related orders (if any) happen to be in cache; the list could be empty. Meanwhile, the attached EntityManager asks the server for the related orders in background. When the orders arrived, the EntityManager populates the list. The list raises events. A watchful UI could respond to those events by filling the screen with orders. Notice that the get implementation delegates to the PropertyMetaData class just as the data property did. The Orders.GetValue is a pipeline the developer can control and intercept with logic of his own. Notice that there is no set. The Order property is read-only. Orders is a collection navigation property, a navigation returning a list of entities. The developer can add and remove entities from the list but cannot replace the list itself. There is another kind of navigation property - the reference navigation property that returns a single entity. This particular Customer entity doesnt have a reference navigation. We suspect that Order does have a reference navigation property one that navigates back from the order to its parent Customer. We suspect such a property exists but we cannot be sure without looking. Navigation properties are optional. You can choose to omit navigation properties from code generation. The association between Customer and Order would remain even if you opted out of both navigation properties. In this case, Order does have a Customer property. Here is Orders Customer property C# [Bindable(false)] [Display(Name="Customer", AutoGenerateField=false)] [DataMember] [IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole2)] public Customer Customer { get { return PropertyMetadata.Customer.GetValue(this); } set { PropertyMetadata.Customer.SetValue(this, value); } } VB <Bindable(False), Display(Name:="Customer", AutoGenerateField:=False), _ DataMember, IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole2)> Public Property Customer() As Customer Get Return PropertyMetadata.Customer.GetValue(Me) End Get Set(ByVal value As Customer) PropertyMetadata.Customer.SetValue(Me, value) End Set End Property It has both a get and a set. That means we could set the parent Customer as well as retrieve it. The get could trigger a lazy customer load if the parent customer is not in cache. DevForce can also lazy load a reference navigation property asynchronously in Silverlight. The property immediately returns a special form of the Customer entity instance called a PendingEntity . This instance has placeholder values until the actual customer data arrive from the server. A reference navigation property always returns a value, never a null. If an order has no parent customer, the property returns a special Customer entity called the Null Entity (aka, nullo). The null entity reduces the likelihood of null reference exceptions and eliminates much of the null value checking that plagues the typical code base. The null entity has permanent placeholder values; all of its reference navigation properties return null entities as well so you can have long chains of navigations (order.Customer.SalesPerson.Name) without incurring a null reference exception. You can always as an entity if it is the null entity. Finally, the promised word about Associations. Associations (Relations) are inherently bidirectional. A Customer has Orders; each Order has a Customer. When defining a navigation property we have to say which way were going: from customer to orders or from orders to customer. The Role (either Role1 or Role2) indicates the direction. You can usually guess which entity type is Role1 and which is Role2 by the name of the RelationProperty (e.g., FK_Order_Customer). To be certain, you must look at actual definition of that RelationProperty in the generated class called EntityRelation. Youll always find the EntityRelation class at the bottom of the generated code file. DevForce supports the same cardinality of Associations as Entity Framework. They can be 1 1, 0..1 1, 1 0..1, 0..1 M, M 0..1, and M M. Yes, DevForce supports many-to-many, unlike WCF RIA Services.
Restore Entity Framework code generation Last modified on March 25, 2011 15:41 DevForce "turns off" Entity Framework code generation by clearing the Custom Tool property setting associated with the EDMX file. If you need to re-enable Entity Framework code generation, open the EDMX file in the EDM Designer and set the DevForce Enabled flag to false, then save the file. This action resets the Custom Tool to EntityModelCodeGenerator, removes the DevForce-specific files, and runs Entity Framework code generation. Your DevForce application won't work with entities generated by the Entity Frameworks' templates. Remember to reset the DevForce Enabled flag back to true to re-enable DevForce code generation. Customizing the DevForce code generation template Customize the DevForce code generation template when you need fine control over the entity class code generated for your Entity Data Model (EDM).
DevForce code generation uses the .NET Text Template Transformation Toolkit (T4) to customize code generation. DevForce ships with a default T4 template that is used for all code generation. Like most T4 templates provided by Microsoft and other vendors, this template can be replaced with a custom version. Custom versions are usually a copy of the default template with some minor modifications. While DevForce supports this approach, we provide what we believe to be a better approach. Instead of modifying the template, you subclass the core template generator class, DomainModelTemplate, overriding only those portions which requires custom treatment. Virtually every major code generation region within your generated code corresponds to a virtual method within this class. Deriving from DomainModelTemplate is much easier than writing raw T4 source code: You write in a familiar programming language You'll have substantially less code to maintain. IdeaBlade frequently releases improved versions of DomainModelTemplate with new features and fixes. DomainModelTemplate can generate either C# or VB code from a single template file. Debugging a DevForce template is substantially easier than debugging a standard T4 template. The source code for DomainModelTemplate is shipped with DevForce; find it in the DevForce installation directory. The DevForce template When you add an Entity Framework EDMX, DevForce adds its T4 template to the project, modifying it slightly to reflect the specifics of your model. A typical example looks something like the following: T4 <#@ template language="C#" debug="true" hostSpecific="true" #> <#@ output extension="./DontReadMe" #> <#@ Assembly Name="System.Core.dll" #> <#@ Assembly Name="IdeaBlade.VisualStudio.DTE.dll" #> <#@ Assembly Name="IdeaBlade.VisualStudio.OM.CodeGenerator.dll" #> <#@ Assembly Name="IdeaBlade.EntityModel.Edm.Metadata.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Reflection" #> <#@ import namespace="IdeaBlade.VisualStudio.DTE" #> <#@ import namespace="IdeaBlade.VisualStudio.OM.CodeGenerator" #> <#@ import namespace="IdeaBlade.EntityModel.Edm.Metadata" #> <# // Source for this file located at: // C:\Users\...\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\ // IdeaBlade OM Designer Extension\6.0.5.0\DomainModelTemplate.tt // SimpleNorthwind.edmx <--- Needed so "Transform Related Text Templates On Save" works correctly. var template = new DomainModelTemplate(this); template.Generate(); #> <#+ #> The template delegates the work to IdeaBlade.VisualStudio.OM.CodeGenerator.DomainModelTemplate class about five lines from the bottom.. There are three steps to customizing this template: 1. Subclass DomainModelTemplate within the existing .tt file. 2. Make a template from the modified .tt file 3. Move your custom DomainModelTemplate to its own assembly. You get a working template at every step. You can stop after steps #1 or step #2 if you wish. We recommend you go all the way to step #3. Before starting, you must install the Visual Studio 2010 SDK to get the Microsoft.VisualStudio.TextTemplating library. Install the right SDK for you version of Visual Studio, either Visual Studio 2010 or Visual Studio 2010 SP1. #1 Subclass inside the .tt file You are going to subclass the DomainModelTemplate class inside the project's .tt file. First, add two assembly references Microsoft.VisualStudio.TextTemplating.10.0 IdeaBlade.Core Next, find the declaration of the template variable and set it with your new template generator class Finally, write the code for your template generator class directly below the old code as seen in this example: T4 <#@ template language="C#" debug="true" hostSpecific="true" #> <#@ output extension="./DontReadMe" #> <#@ Assembly Name="Microsoft.VisualStudio.TextTemplating.10.0" #> <#@ Assembly Name="IdeaBlade.Core" #> ... // SimpleNorthwind.edmx <--- Needed so "Transform Related Text Templates On Save" works correctly. var template = new MyTemplate(this); template.Generate(); #> <#+ public class MyTemplate : DomainModelTemplate {
public MyTemplate(Microsoft.VisualStudio.TextTemplating.TextTransformation textTransformation) : base(textTransformation) {}
protected override void WriteEntityDataPropertyAttributes(EdmPropertyWrapper property) { WriteAttribute("Foo(" + Quote(property.Name) + ")"); base.WriteEntityDataPropertyAttributes(property); } } #> This customization simply adds a Foo attribute to the top of every data entity property. MyTemplate() replaces DomainModelTemplate and overrides the base WriteEntityDataPropertyAttributes method to add Foo. See the topic on adding a custom base class for a more realistic example of a template method override that also makes use of the Tag EDM extension property. While writing the MyTemplate class within the template itself is certainly easy to do, we don't recommended it. First, this customization only works for the specific EDMX to which it is attached. SimpleNorthwind.edmx, the name of this project's EDMX file, is baked into the .tt file. If you change the name of the EDMX file you'll have to change change this .tt file as well. You won't be able to re-use this custom MyTemplate class for other models and applications. Second, you don't get any help from Intellisense while you're writing MyTemplate; it's difficult to edit a template file with any significant amount of code this way. Let's fix the first problem first. #2 Make a template from the modified .tt file The DevForce EDM Designer Extension looks for a file named DomainModelTemplate.tt in a well-known place. DomainModelTemplate.tt is actually a template for making .tt templates. It has placeholder tokens for model-specific values. It's the file the DevForce Designer Extension used to create the SimpleNorthwind.edmx.tt file in the model project. The goal is to replace the existing DomainModelTemplate.tt file with your version so that the next time DevForce generates a entity classes from a model ... any model ... it creates a .tt file based on your template file. DomainModelTemplate.tt is in another directory, the one mentioned in a comment of the .tt you edited. TT // Source for this file located at: // C:\Users\...\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\ // IdeaBlade OM Designer Extension\6.0.5.0\DomainModelTemplate.tt The actual directory location varies according to machine, operating system, and user name. Copy the model project .tt file that you just edited into template directory Backup the current DomainModelTemplate.tt by renaming it (e.g., DomainModelTemplate.tt.backup) Rename your modified .tt file to DomainModelTemplate.tt Now edit this new version of DomainModelTemplate.tt to put in the placeholder tokens. Change: TT // Source for this file located at: // C:\Users\...\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\ // IdeaBlade OM Designer Extension\6.0.5.0\DomainModelTemplate.tt // SimpleNorthwind.edmx <--- Needed so "Transform Related Text Templates On Save" works correctly. To: TT // Source for this file located at: $templateFullFileName$ // $edmxname$ <--- Needed so "Transform Related Text Templates On Save" works correctly. All future .tt files created for any model project will be based on this new template. You can also update an older project that was generated with the old template version. delete its .tt file make a fake change to the EDMX file (e.g., move an object on the canvas) save This triggers recreation of the EDMX's .tt file and regenerates your model using the new template. #3 Move your custom generator to its own assembly This third step builds on the second. The goal is to be able to edit your custom template generator in Visual Studio so you can take advantage of Intellisense, type-checking, and Visual Studio debugging. You will extract your custom MyTemplate class from the template and put it in its own assembly. Create a new project. Let's call it MyTemplateAssembly. Then create the MyTemplate class from the code you wrote earlier. When adding references, you can find the IdeaBlade.VisualStudio.OM.CodeGenerator assembly in the DesktopAssemblies folder where DevForce is installed. e.g. C:/Program Files/DevForce 2010/DesktopAssemblies C# using IdeaBlade.EntityModel.Edm.Metadata; using IdeaBlade.VisualStudio.OM.CodeGenerator;
namespace MyTemplateAssembly { public class MyTemplate : DomainModelTemplate {
public MyTemplate(Microsoft.VisualStudio.TextTemplating.TextTransformation textTransformation) : base(textTransformation) {}
Public Class MyTemplate Inherits DomainModelTemplate Public Sub New(ByVal textTransformation As _ Microsoft.VisualStudio.TextTemplating.TextTransformation) MyBase.New(textTransformation) End Sub
Protected Overrides Sub WriteEntityDataPropertyAttributes( _ ByVal property1 As EdmPropertyWrapper) WriteAttribute("Foo(" & Quote(property1.Name) & ")") MyBase.WriteEntityDataPropertyAttributes(property1) End Sub End Class Make a new version of DomainModelTemplate.tt from the original backup copy. Edit this DomainModelTemplate.tt file to use your custom template generator. Add a reference to the custom template generator's assembly Replace usage of the DevForce DomainModelTemplate class with your class. The final DomainModelTemplate.tt would look something like this: T4 <#@ template language="C#" debug="true" hostSpecific="true" #> <#@ output extension="./DontReadMe" #> <#@ Assembly Name=,path to MyTemplateAssembly- #> <#@ Assembly Name="System.Core.dll" #> <#@ Assembly Name="IdeaBlade.VisualStudio.DTE.dll" #> <#@ Assembly Name="IdeaBlade.VisualStudio.OM.CodeGenerator.dll" #> <#@ Assembly Name="IdeaBlade.EntityModel.Edm.Metadata.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Reflection" #> <#@ import namespace="IdeaBlade.VisualStudio.DTE" #> <#@ import namespace="IdeaBlade.VisualStudio.OM.CodeGenerator" #> <#@ import namespace="IdeaBlade.EntityModel.Edm.Metadata" #> <# // Source for this file located at: $templateFullFileName$ // $edmxname$ <--- Needed so "Transform Related Text Templates On Save" works correctly. var template = new MyTemplate(this); template.Generate(); #> <#+ #> Notice that you must spell out the full path to your assembly reference: <#@ Assembly Name={path to MyTemplateAssembly} #>/ You could sign MyTemplateAssembly and install it in the GAC. Then you could refer to it as you do regular .NET and IdeaBlade assemblies: <#@ Assembly Name=MyTemplateAssembly #>. This might make it easier to share with other developers on your team. Congratulations! You're done! Future .tt files created for a model project will be based on your new template and you'll be able to maintain that template efficiently in Visual Studio. Customize the model The generated entity model is typically extended with additional business logic such as validation rules, UI hints, calculated properties and workflow methods. This topic introduces some of these customization techniques.
Limits of code generation The generated entity model classes are the product of the DevForce T4 template (the .tt file) and the conceptual entity metadata inscribed in the edmx file. The template prescribes how classes and properties are written. The metadata describe what classes and properties are written. The EDM Designer, as extended by DevForce, gives you some control over aspects of the classes and their properties, in particular their accessibility, UI hints and schema-determined validation attributes. But you are limited to describing persistent classes and their data and navigation properties - the values stored in database tables and the pathways to related entities. There is no place in the EDMX to specify constructors, calculations, events, workflow methods or validation rules, no way to define classes or properties that aren't directly translatable to data storage.
You can modify the T4 template (the .tt file) to change how the classes are implemented. That's not an effective instrument for detailing the semantics of individual entity classes. When you need the Customer class to be more than a bag of persistent data (CustomerID, CompanyName, etc.), to have behavior that expresses what it means to be a "customer" in the application domain, you'll extend the Customer class with handwritten, custom code. Customize Entities You don't customize the entity model by modifying the generated code; you'd lose those changes the next time you ran the code generator. You cannot extend generated entity classes by subclassing them. Neither DevForce nor Entity Framework will work with derived entity classes. Instead you customize by writing code in one or all of three places: 1. A partial class file 2. A metadata class file (the "buddy class") 3. A helper class to which the entity delegates, perhaps for validation or property interception. Add a partial class file The DevForce T4 code generator emits a partial classes. You can add capabilities to the generated entity classes by writing supplemental code in your own partial class files.
You add partial class files to the model project, knowing that the compiler will combine the class definitions it finds in all source code files when it builds the final entity classes. All partial class source code files must be in the same project as the generated class files. To illustrate this technique of extending the generated entity classes, we'll add a Customer partial class that enhances the generated Customer class. Begin by adding a new source code file to the same model project calling it Customer.cs (or Customer.vb). To this file you could add a Customer class that looks like this one: C# [DebuggerDisplay("{ToString()}")] [RequiresAuthentication] public partial class Customer { public Customer() { CustomerID = SystemGuid.NewGuid(); }
[Display(Name = "Original", AutoGenerateField = true, Order = 3)] public bool IsOriginal { get { return CustomerID_OLD != String.Empty; } }
public bool CanDelete { get { return !IsOriginal; }}
// Enforce integrity of order before adding public void AddOrder(Order order) {/*...*/}
// Enforce rules for removing an order public void RemoveOrder(Order order) {/*...*/}
// Trim spaces from Company name [BeforeSet(EntityPropertyNames.CompanyName)] public void TrimNameBeforeSet( PropertyInterceptorArgs<Customer, String> args) { if (args.Value != null) args.Value = args.Value.Trim(); }
public override string ToString() { return String.Format("{0}({1})", CompanyName, CustomerID); } } VB <DebuggerDisplay("{ToString()}")> <RequiresAuthentication> _ Partial Public Class Customer Public Sub New() CustomerID = SystemGuid.NewGuid() End Sub
<Display(Name := "Original", AutoGenerateField := True, Order := 3)> Public ReadOnly Property IsOriginal() As Boolean Get Return CustomerID_OLD <> String.Empty End Get End Property
Public ReadOnly Property CanDelete() As Boolean Get Return Not IsOriginal End Get End Property
' Enforce integrity of order before adding Public Sub AddOrder(ByVal order As Order) '... End Sub
' Enforce rules for removing an order Public Sub RemoveOrder(ByVal order As Order) '... End Sub
' Trim spaces from Company name <BeforeSet(EntityPropertyNames.CompanyName)> Public Sub TrimNameBeforeSet(ByVal args As PropertyInterceptorArgs(Of Customer, String)) If args.Value IsNot Nothing Then args.Value = args.Value.Trim() End If End Sub
Public Overrides Function ToString() As String Return String.Format("{0}({1})", CompanyName, CustomerID) End Function End Class The example demonstrates many of the kinds of customizations you might make such as: The class-level RequiresAuthentication attribute - one of several security attributes; - ensures that only authenticated users can query or save Customers. An explicit default constructor initializes the Guid EntityKey for new customers. A custom, read-only IsOriginal property that reports if this Customer is one of the original Northwind customers. The Display attribute hints to the UI how to label and position the IsOriginal property. CanDelete, AddOrder and RemoveOrder express workflow and integrity rules. An attributed property interceptor trims spaces from ends of input CompanyName. The ToString overrides is especially useful during debugging. The class-level DebuggerDisplay attribute delivers a friendlier value in the debugger. Adding constructors Like any .NET class, the compiler imputes a public default, parameterless constructor unless you write a constructor of your own. DevForce doesn't generate a constructor. But you can write one (as shown in the example) and the partial class is a good place to do it. You might even write a constructor with parameters in which case you must also add a default, parameterless constructor. Another topic covers writing entity constructors in greater detail. For now we'll just call out the importance of having a public default constructor. Link to the Silverlight application We can't use the .NET entity classes in a Silverlight application. The Silverlight application has to have its own version of those classes. To ensure the fidelity of definitions in both .NET and Silverlight environments, the Silverlight model project link to the source code files in the .NET model project. DevForce takes care of linking to the generated source code files automatically. The developer must link to custom source code files manually. Limits of the partial class A partial class can add new features but it cannot change existing features of a generated class. You can't make a public property private. You can't change its implementation either. But you can "re-attribute" them in a metadata "buddy" class. Link to source files This topic describes how to link to .NET model source code files from a Silverlight model project and why you'd want to do that.
We can't use .NET entity classes in a Silverlight application. While Silverlight and .NET projects are written in the same .NET languages, they don't share the same supporting libraries. While they both have libraries with the same names and the same functions, these are not the same assemblies. Silverlight can't make use of any full .NET library. That's why a Silverlight application can't consume the entity classes in your full .NET entity model. This fact causes serious pain. When you are responsible for building an entire data-driven, business application, back to front, from server to Silverlight client, you want one implementation of each conceptual entity. You don't want two versions: one in Silverlight and one in full .NET. Coordinating entity definitions You could rely on code-generation magic to automatically write and rewrite the sort-of- similar-but-not-really Silverlight versions of the full .NET entities. This is the approach taken by WCF RIA Services. The DevForce takes a different tack. DevForce strives for perfect fidelity between Silverlight and .NET class definitions by ensuring that the source code is the same source code for entities compiled on both sides. That guarantee is made possible by link items in the Silverlight model project that point to corresponding entity source code files in the .NET model project. Changes to an entity class file, whether made from within the Silverlight project or from within the .NET project, are actually changes to the same physical source code file. When the projects are (re)compiled, both Silverlight and .NET entity classes are built from the same source code. The resulting classes are as identical as they can be. Linking This screenshot shows generated entity class source files in a web application project linked to the Silverlight application project:
Notice the shortcut symbol on the Silverlight project item name indicating that the item is a link.
Automatic linking DevForce will automatically generate a link to your auto-generated model and developer classes, using the following rules: 1. If only a single Silverlight project is in the solution, it will be the link target. 2. Assembly name "matching" is done: DevForce will look for the first Silverlight project (in assembly name order) whose assembly name starts with the server project's "root" assembly name. This "root" name is the server project's assembly name with any trailing "Web" or ".Web" removed. For example, if the server project assembly name is "MyApplication.Web", DevForce will look for a Silverlight project whose assembly name starts with "MyApplication". 3. If you've manually created or moved a link DevForce will honor that link and not remove or modify it. If a target project can't be found then a message will be written to the output window. You can still manually create the link, as discussed below. Manual linking DevForce takes care of linking the Silverlight project to the .NET generated source code files. The developer must manually link the source code files - such as partial class files - that he creates on his own. You create the link - once - by doing the following: Select the Silverlight project Right-mouse-click and select Add | Existing item... (or press Ctrl-Shift-A) Navigate to the web application project's directory Scatter select the code files to link (hold down Ctrl key while clicking each file) Do not click the Add button. To the right of the Add button is a drop-down arrow; click that to reveal "Add" menu choices
Pick "Add As Link" The selected files are now linked in the Silverlight application as we see here with Customer and Department partial class files.
Preserving differences deliberately Sometimes you have business logic that should only execute on one side or the other. DevForce makes full type fidelity possible but it doesn't mandate it. You could use compiler directives within a source code file to include or exclude statements that only apply in Silverlight or .NET. A cleaner and easier way is to create additional partial class files, either in the .NET project or in the Silverlight project, and deliberately not link them. It's a good idea to give the files special names that announce your intention and prevent confusion. IdeaBlade uses the ".Desktop" and ".SL" extension conventions for this purpose. Customer.Desktop and Customer.SL are suggested names for Customer class files containing code to run only in full .NET or only in Silverlight. Add a Metadata class The EntityMetadata class gives you complete control over the attributes that adorn the generated entity class properties.
Attributes are an increasingly popular means of adding information and behavior to the members of a class. Many UI technologies, WPF and Silverlight controls in particular, inspect the data-bound entity properties, looking for UI hint attributes such as Display. The vocabulary of UI hint attributes is rich and growing. Validation engines on both client and server also inspect entities for validation attributes such as Required; the engines turn these attributes into rules that they apply when validating entities. The DevForce code generator adds many such attributes to the generated classes. But it probably won't get all of the ones that you care about and you may want to impose different values for some of the attributes DevForce does supply. The DevForce EDM Designer Extension gives you some control over the generated attributes but not as much control as you might need. Once the classes have been generated, their attributes are baked in. There is no direct way to add unanticipated attributes to the generated property. The code generator doesn't know about them. Again, once the classes have been generated, their attributes are baked in. A property with a problem For example, here is the CompanyName property generated for the Customer class. C# /// <summary>Gets or sets the CompanyName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="CompanyName", AutoGenerateField=true)] [IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } VB ''' <summary>Gets or sets the CompanyName. </summary> <Bindable(True, BindingDirection.TwoWay), Editable(True), _ Display(Name:="CompanyName", AutoGenerateField:=True), _ IbVal.StringLengthVerifier(MaxValue:=40, IsRequired:=True, _ ErrorMessageResourceName:="Customer_CompanyName"), DataMember> Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property We might prefer a different label than "CompanyName" and want automatic layout controls to position the name near the front or top. Write a metadata class Fortunately, you can write an entity metadata class (aka, the "buddy" class) to specify precisely the attributes that should be applied to a designated property. You can add any attribute to a generated property, including custom attributes that you wrote. And you can remove or replace attributes with the help of the metadata class The DevForce T4 code generator looks for a metadata class associated with a class it is generating. If it sees a public field or property name in that buddy class that matches the name of a property it would generate, it applies the attributes on that buddy class property instead of the attributes it would otherwise have generated. C# [MetadataType(typeof(Customer.CustomerMetadata))] [DebuggerDisplay("{ToString()}")] public partial class Customer { //...
public static class CustomerMetadata { [Display(Name = "ID", AutoGenerateField = true, Order = 0)] [Editable(false)] public static Guid CustomerID;
[Display(Name = "Name", AutoGenerateField = true, Order = 1)] [DoNothingCustom] public static string CompanyName;
// Country is nullable in database but we want to require it [RequiredValueVerifier] public static string Country;
} VB <MetadataType(GetType(Customer.CustomerMetadata)), _ DebuggerDisplay("{ToString()}")> Partial Public Class Customer '...
Public NotInheritable Class CustomerMetadata <Display(Name := "ID", AutoGenerateField := True, Order := 0), _ Editable(False)> Public Shared CustomerID As Guid
<Display(Name := "Name", AutoGenerateField := True, Order := 1), _ DoNothingCustom> Public Shared CompanyName As String
' Country is nullable in database but we want to require it <RequiredValueVerifier> Public Shared Country As String
<Bindable(True, BindingDirection.OneWay), Editable(False), _ Display(Name := "CustomerID_OLD", AutoGenerateField := False)> Public Shared CustomerID_OLD As Guid End Class
End Class Some observations: The MetadataType attribute is essential. It tells DevForce that a buddy class exists and where to find it. The metadata class is an inner static class in this example. Itcould stand on its own, perhaps in a separate code file. The metadata class is public as are its properties and fields. The Editable attributes warns the UI not to facilitate changing the key (CustomerID) The Display attribute specifies the desired label and relative position of the ID field if the UI will be laying out the view automatically. CompanyName gets a label change and its own custom attribute, DoNothingCustom. It may mean nothing to .NET controls but it might mean something to your code. The Country property is nullable in the database; the RequiredValueVerifier ensures that it must have a value anyway if the Customer is to be saved. The attributes on CustomerID_OLD instruct the UI to hide that property and forbid change if possible. Re-running the code generator with this metadata class in place causes DevForce to produce different code for these properties than it would have otherwise. Here's the CompanyName property for example: C# /// <summary>Gets or sets the CompanyName. </summary> [Display(Name = "Name", AutoGenerateField = true, Order = 1)] [SimpleNorthwindModel.DoNothingCustom] [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } VB ''' <summary>Gets or sets the CompanyName. </summary> <Display(Name := "Name", AutoGenerateField := True, _ Order := 1), SimpleNorthwindModel.DoNothingCustom, Bindable(True, _ BindingDirection.TwoWay), Editable(True), IbVal.StringLengthVerifier(MaxValue:=40, _ IsRequired:=True, ErrorMessageResourceName:="Customer_CompanyName"), DataMember> Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property See the revised Display and new DoNothingCustom attributes. We didn't touch the validation attributes so the templated validation remains in place. DevForce does not re-generate the model automatically after changes to the metadata class. REMEMBER to re-run the code generation tool after changing the metadata class so that its attributes are merged into the generated class code. Link to Silverlight? In this example, the metadata class is an inner class of the custom Customer class located in a partial class file. The Silverlight model project is presumably already linked to this partial class as we discussed in the partial class topic so there's no additional linking required. What if we had put the metadata class in a separate class file? Should the Silverlight project link to it then? It depends. There's no need if the metadata class file contains nothing but attributes adorning static fields or properties; DevForce code-generation will have baked their effects into the generated classes already; the metadata class has served its purpose. On the other hand, if the metadata class defines other business logic, such as the validation rules in the Customer class example, then you must link to this metadata class manually in order to make that logic available on the Silverlight client. Add custom base class Generated entity classes ultimately inherit from Entity but you can inject your own custom entity base class at the root of your entity model's inheritance hierarchy in order to provide common state and behaviors across the model. This topic explains how.
In many applications there is logic that every entity should share. It's natural to put this logic in a base class and have all of your entity classes inherit from it. A wrong way You happen to know that when an entity class inherits from another class, you specify the base class in the EDM Designer Base Type property. You consider creating an empty, abstract EntityBase type in the conceptual model and setting every entity's Base Type property to "EntityBase". That won't work for several reasons chief among them: Entity Framework insists that all entity types be mapped to a store object. There is no store object for EntityBase and you can't create one either. Name it in Injected Base Type The DevForce EDM Designer Extension added many code generation control properties including a model-level Injected Base Type property. You set the Injected Base Type to "EntityBase" in the EDM Designer Properties Window as shown:
Base type code-generation The DevForce code generator substitutes EntityBase wherever it would have specified the DevForce Entity class. C# public partial class Customer : EntityBase {...} VB Partial Public Class Customer Inherits EntityBase ... End Class It also adds to the entity source code file an empty EntityBase partial abstract class that inherits from Entity: C# using IbEm = IdeaBlade.EntityModel; ... [DataContract(IsReference=true)] [IbEm.DiscoverableType(IbEm.DiscoverableTypeMode.KnownType)] public abstract partial class EntityBase : IbEm.Entity { } VB Imports IbEm = IdeaBlade.EntityModel ... <DataContract(IsReference=True)> <IbEm.DiscoverableType(IbEm.DiscoverableTypeMode.KnownType)> Partial Public MustInherit Class EntityBase Inherits IbEm.Entity EndClass Add code to EntityBase Now add the logic that you want all entities to share ... but not to the generated EntityBase class! Your changes would be lost the next time you re-generated the entity class file ... which happens often. Instead, follow the same pattern for extending generated entity classes: add a partial class file called EntityBase.cs (or EntityBase.vb). Remember to link to it in your Silverlight model project. Here's an example with a dummy property: C# public partial class EntityBase { /// <summary> /// <see cref="EntityBase"/> demo property that gets the name of the concrete type. /// </summary> protected internal string EntityTypeName { get { return This.GetType().Name; } } } VB Partial Public Class EntityBase ''' <summary> ''' <see cref="EntityBase"/> demo property that gets the name of the concrete type. ''' </summary> Protected Friend ReadOnly Property EntityTypeName As String Get Return Me.GetType().Name End Get End Property End Class Base class in a different assembly You can define the EntityBase class in a different assembly if you want to share it with multiple models in multiple projects. You might even use EntityBase in different DevForce applications. DevForce has a naming convention for that purpose. If the name supplied to the Injected Base Type EDM Designer extension property contains a period (.), DevForce assumes that the named class already exists elsewhere and uses the name exactly as you specified it. Suppose you defined EntityBase in a class library called Common. Presumably its namespace is also called Common. Set the I njected Base Typeto "Common.EntityBase". Add a reference to the Common library to your model project. DevForce will generate entities that inherit from Common.EntityBase. Imitate the generated EntityBase if you decide to write your own in a different project. Remember to inherit from Entity and include the two class-level attributes shown above. Base class for specific entity classes Perhaps you want a few of your entity classes to inherit from a special base class dedicated just to them. For example, suppose that Customer and Employee have functionality in common as "actors" in the domain. These classes have no properties in common and they do not inherit from another entity model class (i.e., they are not involved in any form of Entity Framework inheritance). For reasons known only to you they have some business logic in common. That logic is irrelevant or incorrect for all other model entities so you refuse to put that logic in the model-wide EntityBase class. You've already defined an abstract Actor class to hold this logic (see below). Now you want to make Customer and Employee inherit from Actor. You'll have to modify the DevForce code generation template to do it. You can't specify a custom base class for individual entity types in the designer. You will be tempted by the EDM Base Type property. It won't work. That property is constrained to other types in the entity model. Fortunately, it's not difficult. Follow the steps for customizing the DevForce T4 template Override the DomainModelTemplate.GetEntityClassDef method as shown below Set the EDM Tag property of both Customer and Employee to "Actor"; your custom template will know to use the Actor class when it sees this Tag.
Here is the suggested method override: C# protected override ClassDef GetEntityClassDef (EntityOrComplexTypeWrapper entityOrComplexType) { String baseName;
var entityType = entityOrComplexType as EntityTypeWrapper;
protected static string GetMyEntityBaseTypeName(EntityTypeWrapper entityType) { if (entityType.Tag.Contains("Actor")) return "Actor"; // ToDo: replace magic string return GetEntityBaseTypeName(entityType); } VB Protected Overrides Function GetEntityClassDef(ByVal entityOrComplexType As _ EntityOrComplexTypeWrapper) As ClassDef Dim baseName As String
Dim entityType = TryCast(entityOrComplexType, EntityTypeWrapper)
Dim isAbstract As Boolean If entityType IsNot Nothing Then baseName = GetMyEntityBaseTypeName(entityType) ' <-- substitute 'baseName = GetEntityBaseTypeName(entityType); // <-- original isAbstract = entityType.IsAbstract Else baseName = "IbEm.ComplexObject" isAbstract = False End If Dim classDef = New ClassDef(FmtName(entityOrComplexType.Name), FmtName(baseName), _ entityOrComplexType.Accessibility).SetAbstract(isAbstract).SetPartial(True) Return classDef End Function
Protected Shared Function GetMyEntityBaseTypeName(ByVal _ entityType As EntityTypeWrapper) As String If entityType.Tag.Contains("Actor") Then ' ToDo: replace magic string Return "Actor" End If Return GetEntityBaseTypeName(entityType) End Function Notice that the GetMyEntityBaseTypeName method checks for the word "Actor" in the Tag property that DevForce added to the EDM Designer properties. If the Tag contains "Actor" (as it will for Customer), the method returns the "Actor" base name ... and Customer inherits from Actor. If it doesn't (as Order does not), the method returns the injected base type name - "EntityBase" in this case - and Order inherits directly from EntityBase. The Tag property is a great way to tell your custom code generation method what to do on a case-by-case basis. It's easy to set from within the EDM Designer. Important: The Actor class must inherit from the DevForce Entity class. It probably will inherit from your EntityBase which inherits from Entity. C# [DataContract(IsReference = true)] [IbEm.DiscoverableType(IbEm.DiscoverableTypeMode.KnownType)] public abstract class Actor : EntityBase { ... } VB <DataContract(IsReference = True)> <IbEm.DiscoverableType(IbEm.DiscoverableTypeMode.KnownType)> Public MustInherit Class Actor Inherits EntityBase End Class Do not insert another entity model class between Actor and Entity. The Entity Framework demands an unbroken chain of entity inheritance within the model. Customer->Actor- >BaseEntity->Entity is OK because, once you get to Actor, there are no more entity model ancestor classes. Customer->Actor->SomeModelEntity->BaseEntity->Entity is not OK. Specify the null entity The null entity (informally known as the nullo) is a special version of an entity class that represents the "entity not found". Every entity class defines its own null entity. You don't have to do anything to define the null entity. The default properties of the nullo are adequate for most purposes. But you can change any property value if you don't like the default by overriding the UpdateNullEntity method in the partial class. An EntityManager will call this method exactly once when it first needs a nullo of the given type. In this example, we make the nullo's Customer.CompanyName property return "N/A". C# public partial class Customer { //...
// Custom implementation for NullEntity protected override void UpdateNullEntity() { this.CompanyName = "N/A"; } } VB Partial Public Class Customer '...
' Custom implementation for NullEntity Protected Overrides Sub UpdateNullEntity() Me.CompanyName = "N/A" End Sub End Class
Property interceptors Last modified on March 14, 2011 12:57 DevForce provides a mechanism to intercept and either modify or extend the behavior of any property on a DevForce entity. This property 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 PropertyInterceptorManager. Attribute interception is substantially easier to write and should be the default choice in most cases.
Attribute interception DevForce provides a mechanism to intercept and either modify or extend the behavior of any property on a DevForce entity. This attribute 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 PropertyInterceptorManager. Attribute interception is substantially easier to write and should be the default choice in most cases. DevForce supplies four attributes that are used to specify where and when property interception should occur. These attributes are: BeforeGetAttribute BeforeSetAttribute AfterGetAttribute 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 auto-generated Employee class. (Generated code) C# public partial class Employee : IdeaBlade.EntityModel.Entity { public string LastName { get { return PropertyMetadata.LastName.GetValue(this); } set { PropertyMetadata.LastName.SetValue(this, value); } } VB Partial Public Class Employee Inherits IdeaBlade.EntityModel.Entity Public Property LastName() As String Get Return PropertyMetadata.LastName.GetValue(Me) End Get
Set(ByVal value As String) PropertyMetadata.LastName.SetValue(Me, value) End Set End Property Property interception of the get portion of this property would be accomplished by adding the following code fragment to a custom Employee partial class definition: C# [AfterGet(EntityPropertyNames.LastName)] public void UppercaseNameAfterGet(PropertyInterceptorArgs<Employee, String> args) { var lastName = args.Value;
if (!String.IsNullOrEmpty(lastName)) { args.Value = args.Value.ToUpper(); } } VB <AfterGet(EntityPropertyNames.LastName)> Public Sub UppercaseNameAfterGet(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 UppercaseNameBeforeSet(IbCore.PropertyInterceptorArgs<Employee, String> args) { var name = args.Value;
if (!String.IsNullOrEmpty(name)) { 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. Define an interceptor to act against multiple types Last modified on March 31, 2011 10:56 You can also define an interceptor to work across more than one type; e.g., against multiple entity types. In this event, you will want to locate the interceptor in a separate (non- entity) class; and you will need to tell DevForce explicitly to discover the interceptors therein in order to activate them. Heres an AfterGet interceptor that intercepts every Get against every property of the Employee and Product entities. (Dont overuse this technique, or you may slow down your app unacceptably!) The interceptor checks to see if the property is a DateTime, or something derived from a DateTime, and that its value is not null. If both things are true, it converts the DateTime to local time. (The interceptor assumes that the DateTime has been stored in the database as a universal time.) C# [IbCore.AfterGet(TargetType = typeof(Employee))] [IbCore.AfterGet(TargetType = typeof(Product))] public static void UniversalGetInterceptor(IbEm.IEntityPropertyInterceptorArgs args) { bool isDateTime = args.EntityProperty.DataType.IsAssignableFrom(typeof(DateTime)); bool valueIsNull = args.Value == null;
if (isDateTime && !valueIsNull) { DateTime aDateTime = ((DateTime)args.Value); aDateTime = aDateTime.ToLocalTime(); args.Value = aDateTime; } } VB <IbCore.AfterGet(TargetType:=GetType(Employee)), _ IbCore.AfterGet(TargetType:=GetType(Product))> _ Public Shared Sub UniversalGetInterceptor(ByVal args As IbEm.IEntityPropertyInterceptorArgs) Dim isDateTime As Boolean = args.EntityProperty.DataType.IsAssignableFrom (GetType(Date)) Dim valueIsNull As Boolean = args.Value Is Nothing
If isDateTime AndAlso (Not valueIsNull) Then Dim aDateTime As Date = (CDate(args.Value)) aDateTime = aDateTime.ToLocalTime() args.Value = aDateTime End If End Sub Note that, since we defined the above interceptor in a non-entity class, we had to specify in the AfterGet attributes the types against which it is to operate: here, Employee and Product. DevForce wont find interceptors defined outside entity classes automatically. In these cases you need to tell the PropertyInterceptorManager where to look. Heres how you let DevForce know youve defined some interceptors in a class named AuxiliaryPropertyInterceptors: C # IbCore.PropertyInterceptorManager.CurrentInstance.DiscoverInterceptorsFromAttributes(typeof( AuxiliaryPropertyInterceptors)); V B IbCore.PropertyInterceptorManager.CurrentInstance.DiscoverInterceptorsFromAttributes(typeof( AuxiliaryPropertyInterceptors))
Dynamic property interception and the PropertyInterceptorManager Last modified on March 24, 2011 13:59 The PropertyInterceptorManager is a singleton, available to the developer via its CurrentInstance property. The current instance is the container for all currently registered interceptor actions. To directly add actions to the PropertyInterceptorManager, use the AddAction method. For example: C# PropertyInterceptorManager.CurrentInstance.AddAction( new PropertyInterceptorAction<PropertyInterceptorArgs<Entity,Object>>( typeof(Entity), null, PropertyInterceptorMode.BeforeSet, (args) => Debug.WriteLine("Entity BeforeSet"), 0.0, "A") ); VB PropertyInterceptorManager.CurrentInstance.AddAction( _ New PropertyInterceptorAction(Of PropertyInterceptorArgs(Of Entity, Object)) _ (GetType(Entity), Nothing, PropertyInterceptorMode.BeforeSet, _ Function(args) Debug.WriteLine("Entity BeforeSet"), 0.0, "A"))
EntityProperties and property interceptors Last modified on March 24, 2011 12:51 Contents Within a DevForce entity, every property has a GetterInterceptor and a SetterInterceptor. These interceptors can be used to add or remove the AddAction actions associated with that property. Under the covers this is going through the PropertyInterceptorManager mechanism, but the syntax is often simpler. For example: C# Employee.PropertyMetadata.Title.GetterInterceptor.AddAction( IbCore.PropertyInterceptorTiming.Before, args => args.Value = args.Value.ToUpper()); VB Employee.PropertyMetadata.Title.GetterInterceptor.AddAction( _ IbCore.PropertyInterceptorTiming.Before, Function(args) _ args.Value = args.Value.ToUpper())
Interceptor chaining and ordering Last modified on March 24, 2011 12:52 Any given property may have more than one interceptor action applied to it. For example: C# #region Chained Interceptors [AfterGet(EntityPropertyNames.LastName)] public void UppercaseLastName( PropertyInterceptorArgs<Employee, String> args) { /// do something interesting } [AfterGet(EntityPropertyNames.LastName)] // same mode (afterGet) and property name as above public void InsureNonEmptyLastName( PropertyInterceptorArgs<Employee, String> args) { // do something else interesting } [AfterGet] // same mode as above and //applying to all properties on employee. public void AfterAnyEmployeeGet( PropertyInterceptorArgs<Employee, Object> args) { // global employee action here } #endregion Chained Interceptors VB <AfterGet(EntityPropertyNames.LastName)> _ Public Sub UppercaseLastName(ByVal args _ As PropertyInterceptorArgs(Of Employee, String)) ''' do something interesting End Sub <AfterGet(EntityPropertyNames.LastName)> _ Public Sub InsureNonEmptyLastName(ByVal args _ As PropertyInterceptorArgs(Of Employee, String)) ' do something else interesting End Sub <AfterGet()> _ Public Sub AfterAnyEmployeeGet(ByVal args _ As PropertyInterceptorArgs(Of Employee, Object)) ' global employee action here End Sub In this case, three different interceptor actions are all registered to occur whenever the Employee.LastName property is called. To execute these actions, the DevForce engine forms a chain where each of the registered interceptor actions is called with the same arguments that were passed to the previous action. Any interceptor can thus change the interceptor arguments in order to change the input to the next interceptor action in the chain. The default order in which interceptor actions are called is defined according to the following rules. 1. Base class interceptor actions before subclass interceptor actions. 2. Named interceptor actions before unnamed interceptor actions. 3. Attribute interceptor actions before dynamic interceptor actions. 4. For attribute interceptor actions, in order of their occurrence in the code. 5. For dynamic interceptor actions, in the order that they were added to the PropertyInterceptorManager. Because of the rigidity of these rules, there is also a provision to override the default order that any interceptor action is called by explicitly setting its Order property. For attribute interceptors this is accomplished as follows: C# [AfterGet(EntityPropertyNames.ContactName, Order=1)] public void ContactNameAfterGet03( IbCore.PropertyInterceptorArgs<Customer, String> args) { ... } VB <BeforeSet(EntityPropertyNames.LastName, Order:=-1.0)> _ Public Sub UppercaseLastName(ByVal args As _ PropertyInterceptorArgs(Of Employee, String)) ' End Sub The Order property is defined as being of type double and is automatically defaulted to a value of 0.0. Any interceptor action with a property of less that 0.0 will thus occur earlier than any interceptors without a specified order and any value greater that 0.0 will correspondingly be called later, and in order of increasing values of the Order parameter. Exact ordering of interceptor actions can thus be accomplished.
Multiple attributes on a single interceptor action Last modified on March 24, 2011 12:56 There will be cases where you want a single interceptor action to handle more than one property but less than an entire class. In this case, it may be useful to write an interceptor action similar to the following: C# [BeforeSet(EntityPropertyNames.FirstName)] [BeforeSet(EntityPropertyNames.LastName)] public void UppercaseName( IbCore.PropertyInterceptorArgs<Employee, String> args) { var name = args.Value; if (!String.IsNullOrEmpty(name)) { args.Value = args.Value.ToUpper(); } } VB <BeforeSet(EntityPropertyNames.FirstName), BeforeSet(EntityPropertyNames.LastName)> _ 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
Named vs. unnamed interceptor actions Last modified on March 24, 2011 12:58 The property interception code snippets in previous topics were all examples of what are termed Named interceptor actions, in that they each specified a single specific named property to be intercepted. It is also possible to create Unnamed interceptor actions that apply to all of the properties for a specific target type. For example, suppose that the following code were implemented in the Employee partial class: C# [BeforeSet] public void BeforeSetAny(IbCore.IPropertyInterceptorArgs args) { if (!Thread.CurrentPrincipal.IsInRole("Administrator")) { throw new InvalidOperationException("Only admistrators can change Product data!"); } } VB <BeforeSet> _ Public Sub BeforeSetAny(ByVal args As IPropertyInterceptorArgs) If Not Thread.CurrentPrincipal.IsInRole("Administrator") Then Throw New InvalidOperationException( _ "Only admistrators can change data") End If End Sub The result of this code would be that only those users logged in as administrators would be allowed to call any property setters within the Employee class. A similar after set action might look like the following: C# [AfterSet] public void AfterSetAny(IbCore.IPropertyInterceptorArgs args) { LogChangeToCustomer(args.Instance); } VB <AfterSet> _ Public Sub AfterSetAny(ByVal args As IPropertyInterceptorArgs) LogChangeToEmployee(args.Instance) End Sub
This would log any changes to the employee class. Later in this document we will also describe how to define interceptors that apply across multiple types as well as multiple properties within a single type. Property interception mechanics Last modified on March 24, 2011 13:00 Property interception within DevForce is accomplished by dynamically generating compiled lamda expressions for each interceptor action. DevForce interceptors are discovered (but not compiled) as each entity class is first referenced. Runtime compilation of each property interceptor occurs lazily the first time each property is accessed. After this first access, the entire code path for each property access is fully compiled. Properties that are never accessed do not require compilation. The addition or removal of interceptor actions after they have been compiled does require a new compilation the next time the property is executed. This happens automatically. Errors encountered during the compilation process will thus appear when a property is accessed for the first time. These exceptions will be of type PropertyInterceptorException and will contain information on the specific method that could not be compiled into a property interceptor action. These are usually a function of a PropertyInterceptorArgs parameter type that is not compatible with the property or properties being accessed. PropertyInterceptor keys Last modified on March 31, 2011 11:41 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. PropertyInterceptorArgs and IPropertyInterceptorArgs Interceptor actions get all of the information about the context of what they are intercepting from the single interceptor argument passed into them. This argument will obviously be different for different contexts; i.e. a set versus a get action, a change to an employee versus a company, a change to the FirstName property instead of the LastName property. Because of this there are many possible implementations of what the single argument passed into any interceptor action might contain. However, all of these implementations implement a single primary interface: IPropertyInterceptorArgs. Every interceptor action shown previously provides an example of this. In each case, a single argument of type PropertyInterceptorArgs<Employee, String of type IPropertyInterceptorArgs> was passed into each of the interceptor methods. In fact, the type of the args instance that is actually be passed into each of these methods at runtime is an instance of a subtype of the argument type declared in the methods signature. For any interceptor action defined on a DevForce entity, the actual args passed into the action will be a concrete implementation of one of the following classes. DataEntityPropertyGetI nterceptorArgs<TI nstance, TValue> DataEntityPropertySetInterceptorArgs<TI nstance, TValue> NavigationEntityPropertyGetI nterceptorArgs<TI nstance, TValue> NavigationEntityPropertySetI nterceptorArgs<TI nstance, TValue> The boldfaced characters above indicate whether we are providing interception to a get or a set property, as well as whether we are intercepting a data or a navigation property. In general, you can write an interception method with an argument type that is any base class of the actual argument type defined for that interceptor. If you do use a base class, then you may need to perform runtime casts in order to access some of the additional properties provided by the specific subclass passed in at runtime. These subclassed properties will be discussed later. The entire inheritance hierarchy for property interceptor arguments is shown below: Assembly Where Defined Property Interceptor Arguments IdeaBlade.Core IPropertyInterceptorArgs IPropertyInterceptorArgs<TInstance, TValue> PropertyInterceptorArgs<TInstance, TValue> IdeaBlade.EntityModel DataEntityPropertyInterceptorArgs<TInstance, TValue> DataEntityPropertyGetInterceptorArgs<TInstance, TValue> DataEntityPropertySetInterceptorArgs<TInstance, TValue> NavigationEntityPropertyInterceptorArgs<TInstance, TValue> NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue> NavigationEntityPropertySetInterceptorArgs<TInstance, TValue> The generic <TInstance> argument will always be the type that the intercepted method will operate on, known elsewhere in this document and the interceptor API as the TargetType. The <TValue> argument will be the type of the property being intercepted. i.e. String for the LastName property. Note that the interceptor arguments defined to operate on DevForce entities break into multiple subclasses with additional associated interfaces based on two primary criteria. 1. Is it a get or a set interceptor? 1. get interceptor args implement IEntityPropertyGetInterceptorArgs 2. set interceptor args implement IEntityPropertySetInterceptorArgs 2. Does it involve a DataEntityProperty or a NavigationEntityProperty?. 1. DataEntityProperty args implement IDataEntityPropertyInterceptorArgs 2. NavigationEntityProperty args implement INavigationEntityPropertyInterceptorArgs The API for each of the interfaces above is discussed below. IPropertyInterceptorArgs The root of all property interceptor arguments is the IPropertyInterceptorArgs interface. Its properties will be available to all interceptors. C# public interface IPropertyInterceptorArgs { Object Instance { get; } Object Value { get; set; } bool Cancel { get; set; } Action<Exception> ExceptionAction { get; set; } object Tag { get; set; } object Context { get; } } VB Public Interface IPropertyInterceptorArgs ReadOnly Property Instance() As Object Property Value() As Object Property Cancel() As Boolean Property ExceptionAction() As Action(Of Exception) Property Tag() As Object ReadOnly Property Context() As Object End Interface In general the most useful of these properties will be the Instance and Value properties. The Instance property will always contain the parent object whose property is being intercepted. The Value will always be the value that is being either retrieved or set. The Cancel property allows you to stop the execution of the property interceptor chain at any point by setting the Cancel property to true. The ExceptionAction property allows you to set up an action that will be performed whenever an exception occurs anywhere after this point in the chain of interceptors. The Tag property is intended as a general purpose grab bag for the developer to use for his/her own purposes. The Context property is used for internal purposes and should be ignored. An example of using the ExceptionAction and Cancel is shown below: Code Listing 8. Order.LogExceptionsAndCancelSets(). Demoed in MainDemo.TestLoggingOfExceptionsAndAutoCancellationOfSets(). C# /// Do not let any setters throw an exception. Instead, eat them, /// log them, and cancel the remainder of the Set operation. [IbCore.BeforeSet(Order = -1)] public void LogExceptionsAndCancelSets( IbCore.IPropertyInterceptorArgs args) { args.ExceptionAction = (e) => { Common.LogManager.LogAnAction(string.Format( "Here in {0}", e.Message)); args.Cancel = true; }; } VB ''' <remarks> ''' Do not let any setters throw an exception. Instead, eat them, ''' log them, and cancel the remainder of the Set operation. ''' </remarks> <IbCore.BeforeSet(Order:=-1)> _ Public Sub LogExceptionsAndCancelSets(ByVal args _ As IbCore.IPropertyInterceptorArgs) args.ExceptionAction = Sub(e) Common.LogManager.LogAnAction( _ String.Format("Here in: {0}", e.Message)) args.Cancel = True End Sub End Sub Note that we applied an explicit Order value less than 0 for this interceptor. Assuming that none of the property-specific interceptors have an explicit Order defined, their Order value defaults to zero, this interceptor will run first for all properties of the type on which its defined. In our samples, this interceptor happens to be defined on the Order type. Please note that there is no relationship between that fact and the use of the Order parameter in the BeforeSet attribute. Two different things! Generic IPropertyInterceptorArgs The following is a generic version of IPropertyInterceptorArgs where both the Instance and Value properties are now strongly typed; otherwise it is identical to IPropertyInterceptorArgs. C# public interface IPropertyInterceptorArgs<TInstance, TValue> : IdeaBlade.Core.IPropertyInterceptorArgs { TInstance Instance { get; } TValue Value { get; set; } bool Cancel { get; set; } Action<Exception> ExceptionAction { get; set; } object Tag { get; set; } object Context { get; } } VB Public Interface IPropertyInterceptorArgs(Of TInstance, TValue) Inherits IdeaBlade.Core.IPropertyInterceptorArgs ReadOnly Property Instance() As TInstance Property Value() As TValue Property Cancel() As Boolean Property ExceptionAction() As Action(Of Exception) Property Tag() As Object ReadOnly Property Context() As Object End Interface IEntity PropertyInterceptorArgs and subclasses Whereas the interfaces above can be used to intercept any property on any object, the argument interfaces below are for use only with DevForce specific entities and complex objects. Each interface below provides additional contextual data to any interceptor actions defined to operate on DevForce entities. The most basic of these is simply the idea that each property on a DevForce entity has a corresponding EntityProperty ( discussed elsewhere in this guide). C# public interface IEntityPropertyInterceptorArgs : IbCore.IPropertyInterceptorArgs { IbEm.EntityProperty EntityProperty { get; } } VB Public Interface IEntityPropertyInterceptorArgs Inherits IbCore.IPropertyInterceptorArgs ReadOnly Property EntityProperty() As IbEm.EntityProperty End Interface An example is shown below: Code Listing 9. Customer.AfterSetAnyUsingIEntityPropertyInterceptorArgs(). C# [IbCore.AfterSet] public void AfterSetAnyUsingIEntityPropertyInterceptorArgs( IbCore.IPropertyInterceptorArgs args) { // Cast the IPropertyInterceptorArgs to the entity-specific version, then // values information available on the EntityProperty contained thereby. var entityPropertyArgs = args as IbEm.IEntityPropertyInterceptorArgs; if (entityPropertyArgs != null) { Common.LogManager.LogAnAction(string.Format( "Property [Customer.{0}] was set to the value: [{1}]", entityPropertyArgs.EntityProperty.Name, args.Value.ToString())); } } VB <AfterSet()> _ Public Sub AfterSetAny(ByVal args As IPropertyInterceptorArgs) Dim entityPropertyArgs = TryCast(args, IEntityPropertyInterceptorArgs) If entityPropertyArgs IsNot Nothing Then Log("The " + entityPropertyArgs.EntityProperty.Name + _ " was set to the value:= " + args.Value.ToString()) End If End Sub The next two interfaces provide additional context based on whether the interceptor action being performed is a get operation or a set operation. For a get operation, IdeaBlade entities have a concept of possibly multiple versions, i.e. an original, current, or proposed version, of an entity at any single point in time. It may be useful to know which version is being retrieved during the current action. Note that the version cannot be changed. C# public interface IEntityPropertyGetInterceptorArgs : IEntityPropertyInterceptorArgs { IbEm.EntityVersion EntityVersion { get; } } VB Public Interface IEntityPropertyGetInterceptorArgs Inherits IEntityPropertyInterceptorArgs ReadOnly Property EntityVersion() As IbEm.EntityVersion End Interface The DevForce EntityProperty is an abstract class with two concrete subclasses; a DataEntityProperty and a NavigationEntityProperty ( discussed elsewhere in this guide). The next two IEntityPropertyInterceptorArgs subinterfaces allow access to instances of one or the other of these depending on whether the property being intercepted is a data or a navigation property. C# public interface IDataEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs { IbEm.DataEntityProperty DataEntityProperty { get; } } VB Public Interface IDataEntityPropertyInterceptorArgs Inherits IEntityPropertyInterceptorArgs ReadOnly Property DataEntityProperty() As _ IbEm.DataEntityProperty End Interface C# public interface INavigationEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs { IbEm.NavigationEntityProperty NavigationEntityProperty { get; } } VB Public Interface INavigationEntityPropertyInterceptorArgs Inherits IEntityPropertyInterceptorArgs ReadOnly Property NavigationEntityProperty() As _ IbEm.NavigationEntityProperty End Interface IPropertyInterceptorArgs Type Coercion One of the first issues that a developer will encounter with writing interceptor actions that handle more than one property is that it becomes difficult or impossible to use a concrete subtype as the argument to the interceptor. For example, imagine that we wanted to write a single action that handled two or more very different properties each of a different type: This could be written as follows: Code Listing 10. Employee.ActionToBePerformedAgainstDifferentTypesV1(). C# [BeforeSet(EntityPropertyNames.HireDate)] // hire date is of type datetime [BeforeSet(EntityPropertyNames.FirstName)] // firstname is of type string public void ActionToBePerformedAgainstDifferentTypesV1( IbCore.IPropertyInterceptorArgs args) { var emp = (Employee)args.Instance; var entityProperty = ((IbEm.IDataEntityPropertyInterceptorArgs) args).EntityProperty; //.. do some action with emp and entityProperty } VB ' hire date is of type datetime ' firstname is of type string <IbCore.BeforeSet(EntityPropertyNames.HireDate)> _ <IbCore.BeforeSet(EntityPropertyNames.FirstName)> _ Public Sub ActionToBePerformedAgainstDifferentTypesV1(ByVal args As _ IbCore.IPropertyInterceptorArgs) Dim emp = DirectCast(args.Instance, Employee) Dim entityProperty = DirectCast(args, _ IbEm.IDataEntityPropertyInterceptorArgs).EntityProperty '.. do some action 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: Code Listing 11. Employee.ActionToBePerformedAgainstDifferentTypesV2(). C# [IbCore.BeforeSet(EntityPropertyNames.HireDate)] // hire date is of type datetime [IbCore.BeforeSet(EntityPropertyNames.FirstName)] // firstname is of type string public void ActionToBePerformedAgainstDifferentTypesV2( IbEm.DataEntityPropertySetInterceptorArgs<Employee, Object> args) { // no casting var emp = args.Instance; var entityProperty = args.DataEntityProperty; //.. do some action with emp and entityProperty } VB ' hire date is of type datetime ' firstname is of type string <IbCore.BeforeSet(EntityPropertyNames.HireDate)> _ <IbCore.BeforeSet(EntityPropertyNames.FirstName)> _ Public Sub ActionToBePerformedAgainstDifferentTypesV2( _ ByVal args As IbEm.DataEntityPropertySetInterceptorArgs( _ Of Employee, [Object])) ' no casting Dim emp = args.Instance Dim entityProperty = args.DataEntityProperty '.. do some action with emp and entityProperty End Sub The problem is that, according to the rules of inheritance, the two concrete classes that this method will be called with: Type 1: DataEntityPropertySetInterceptorArgs<Employee, String> Type 2: DataEntityPropertySetInterceptorArgs<Employee, DateTime> do not inherit from: Type 3: DataEntityPropertySetInterceptorArgs<Employee, Object> In fact, the only class or interface that they do share is: IPropertyInterceptorArgs So in order to allow this construction, DevForce needs to coerce each of Type1 and Type2 into Type3 for the duration of the method call. Because DevForce does do this, any of the following arguments are also valid: Type 4: DataEntityPropertySetInterceptorArgs<Entity, Object> Type 5: DataEntityPropertySetInterceptorArgs<Object, Object> Type 5: PropertyInterceptorArgs<Employee, Object> etc. The basic rule for the type coercion facility is that any concrete type can be specified if its generic version is a subtype of the generic version of the actual argument type that will be passed in.
The EntityPropertyNames class Last modified on March 31, 2011 14:37 In all of the previous examples we have shown Named attributes specified with the form EntityPropertyNames.{PropertyName}. This is a recommended pattern that ensures type safety. However, the following two attribute specifications have exactly the same effect: C# [BeforeSet(EntityPropertyNames.FirstName)]
<BeforeSet("FirstName")> The EntityPropertyNames reference is to a class that is generated (as multiple partial classes) inside the designer code file associated with the EntityModel. Its primary purpose is to allow specification of property names as constants. Note that because EntityPropertyNames is generated as a set of partial classes, you can add your own property names to the class for any custom properties that you create.
Interception method signatures While the property interceptor methods described previously allow a great deal of control over the entire interception process, there are times when this is overkill. Sometimes all you really want is to do is modify or inspect the incoming or outgoing values. In these cases, a simplified signature for an interception method is also provided. Standard interceptor action Code Listing 14. Employee.UppercaseLastName(). C# [IbCore.AfterGet(Employee.EntityPropertyNames.LastName)] public void UppercaseLastName( IbCore.PropertyInterceptorArgs<Employee, String> args) { var lastName = args.Value; if (!String.IsNullOrEmpty(lastName)) { args.Value = args.Value.ToUpper(); } } VB <IbCore.AfterGet(Employee.EntityPropertyNames.LastName)> Public Sub UppercaseLastName(ByVal args As _ IbCore.PropertyInterceptorArgs(Of Employee, String)) Dim lastName = args.Value If Not String.IsNullOrEmpty(lastName) Then args.Value = args.Value.ToUpper() End If End Sub Alternative interceptor action Code Listing 15. Employee.UppercaseLastNameV2(). C# [IbCore.AfterGet(Employee.EntityPropertyNames.LastName)] public String UppercaseLastNameV2(String lastName) { if (!String.IsNullOrEmpty(lastName)) { return lastName.ToUpper(); } else { return String.Empty; } } VB <IbCore.AfterGet(Employee.EntityPropertyNames.LastName)> Public Function UppercaseLastNameV2(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.
PropertyInterceptor attribute discovery Last modified on March 31, 2011 15:03 In general, any interceptor method declared within a DevForce entity and marked with a property interceptor attribute will be automatically discovered before the first property access. PropertyInterceptors will most commonly be defined within the developer-controlled partial class associated with each entity. Property interceptors can also be defined on any base class and these will also be discovered automatically. In order to reduce the surface area of any entity class, a developer may not want to expose the property interceptor methods directly on the surface of his or her class. To facilitate this, DevForce will also probe any public inner classes of any entity class and will locate any property interceptors defined there as well. Example: C# /// </summary> /// <remarks> /// Property interceptors will also be discovered inside internal class /// of the Entity class. /// </remarks> public class PropertyInterceptorsDefinitions { [IbCore.BeforeGet(Employee.EntityPropertyNames.LastName)] public static void LastNameInterceptor (IbEm.IEntityPropertyInterceptorArgs args) { // ... } [IbCore.AfterSet] public static void LoggingInterceptor (IbEm.IEntityPropertyInterceptorArgs args) { // ... } } VB Public Class PropertyInterceptorsDefinitions <IbCore.BeforeGet(Employee.EntityPropertyNames.LastName)> _ Public Shared Sub LastNameInterceptor(ByVal args _ As IbEm.IEntityPropertyInterceptorArgs) ' ... End Sub <IbCore.AfterSet()> _ Public Shared Sub LoggingInterceptor(ByVal args _ As IbEm.IEntityPropertyInterceptorArgs) ' ... End Sub End Class Important note: Property interceptor methods defined on a class directly may be either instance or static methods; whereas property interceptors defined on an inner class (or anywhere other than directly on the entity class) must be static methods. In the event that a developer wants to completely isolate his interception methods in another non-entity-based class, then discovery will not occur automatically. In this case, the DiscoverInterceptorsFromAttributes method on the PropertyInterceptorManager class may be used to force discovery of any specified type and all of its base types. Attribute interceptors that are declared outside of the classes to which they apply must be further qualified via the TargetType property as shown below: C# [IbCore.AfterSet(Employee.EntityPropertyNames.LastName, TargetType = typeof(Employee))] public static void LoggingInterceptor( IbEm.IEntityPropertyInterceptorArgs args) { // ... } VB Public Class UnattachedInterceptor <IbCore.AfterSet(Employee.EntityPropertyNames.LastName, _ TargetType:=GetType(Employee))> Public Shared Sub LoggingInterceptor(ByVal args _ As IbEm.IEntityPropertyInterceptorArgs) ' ... End Sub End Class Examine model metadata Last modified on April 04, 2011 10:08 Contents The EntityMetadataStore Property Metadata Examine entity model metadata programmatically by interrogating the EntityMetadata object held in the EntityMetadataStore singleton.
The EntityMetadataStore DevForce maintains a thread-safe repository of metadata for all types in a domain model. Applications can query the store using the GetEntityMetadata() method for a type: C# EntityMetadata employeeEntityMetaData = EntityMetadataStore.Instance.GetEntityMetadata( typeof(DomainModel.Employee)); VB Dim employeeEntityMetaData As EntityMetadata = _ EntityMetadataStore.Instance.GetEntityMetadata( _ GetType(DomainModel.Employee)) You can also access the EntityMetadata for any entity instance using its EntityAspect: C# var cust = _em1.Customers.First(); var metadata = cust.EntityAspect.EntityMetadata; VB Dim cust = _em1.Customers.First() Dim metadata = cust.EntityAspect.EntityMetadata The EntityMetadata provides the following members:
The table below provides an explanation for a few key members: Property CanQueryByEntityKey Gets whether primary key queries are allowed. Property ComplexTypeProperties Returns a collection of DataEntityProperties that describe complex object properties for entities of this type. Property ConcurrencyProperties Returns a collection of DataEntityProperties that are concurrency properties for entities of this type. Method CreateEntity() Creates a new entity of the type described by this metadata item. Property DataProperties Returns a collection of DataEntityProperties for entities of this type. Property DataSourceKeyName Gets the name of the Data Source Key associated with this type. Property DefaultValueFunction Static - Use to override how DevForce sets the default value for a data entity property. Property EntityProperties Returns a collection of EntityProperties that belong to entities of this type. Property EntityType Gets the type of the entity. Property ForeignKeyProperties Returns a collection of ForegnKey DataEntityProperties Property HasStoreGeneratedId Returns whether this entity type has a store generated id. Property IsComplexObjectType Returns whether this metadata describes a ComplexObject. Property KeyProperties Returns a collection of DataEntityProperties that are keys for entities of this type. Property NavigationProperties Returns a collection of NavigationEntityProperties for entities of this type. Property Metadata When DevForce generates the code for your domain model it includes a nested class called PropertyMetadata within each generated Entity. Within PropertyMetadata are static fields for all the EntityProperty definitions within the entity. Youll see a DataEntityProperty for every non-navigation property, and either a NavigationScalarEntityProperty or NavigationListEntityProperty for all navigation properties. You can easily access these properties at any time via the static field definitions: C# var dataProp = Customer.PropertyMetadata.Id; var navListProp = Customer.PropertyMetadata.OrderSummaries; var navScalarProp = OrderSummary.PropertyMetadata.Customer; VB Dim dataProp = Customer.PropertyMetadata.Id Dim navListProp = Customer.PropertyMetadata.OrderSummaries Dim navScalarProp = OrderSummary.PropertyMetadata.Customer Also generated into each Entity class is a nested class called EntityPropertyNames. This class contains string constants for all the property names within the entity. C# public partial class Employee : IbEm.Entity { public new partial class EntityPropertyNames : IbEm.Entity.EntityPropertyNames { public const String Id = "Id"; public const String LastName = "LastName"; public const String FirstName = "FirstName"; } } VB Partial Public Class Employee Inherits IbEm.Entity Partial Public Shadows Class EntityPropertyNames Inherits IbEm.Entity.EntityPropertyNames Public Const Id As String = "Id" Public Const LastName As String = "LastName" Public Const FirstName As String = "FirstName" End Class End Class
XML serialization of entities Last modified on March 25, 2011 15:46 Entities can be serialized into an XML stream or file for any number of purposes, including exposing these entities from a Web Service, or as the first step in an XSLT transform for reporting or further processing. All entities in the code generated by the Entity Data Model Designer are marked with the DataContract attribute, and all entity properties with the DataMember attribute. These attributes define how an entity will be serialized by a serializer. They are also required by DevForce in an n-tier application to query and save entities across tiers. Serialization using the DataContractSerializer and NetDataContractSerializer is supported. Serialization using the JSON serializer is not supported because the generated DataContract attributes for entities set IsReference to true to preserve object references. Depth of the object graph One issue when serializing an object graph (objects that are connected to other objects, ad- infinitum) has to do with the depth of the object graph that should be serialized. Navigation properties on entities in DevForce are lazily loaded, so you might at first worry that serialization of a single entity could accidentally cause a large object graph to be serialized. Fortunately, DevForce controls this by only serializing entities that are present within the EntityManager's cache at the start of serialization. No queries will be sent to the data source to load navigation properties not yet loaded, so only the data you want will be serialized. For example, if you load an Employee and her Orders into the EntityManager cache and then serialize the Employee object, the result will also include the loaded Orders. If LineItems for the Orders had also been loaded into cache, then the object graph serialized would include them too. Alternately, if only an Employee was in cache without any attached Orders, then only the Employee object would be serialized.
The EntityCacheState Some applications may find the built-in EntityCacheState an easy means of serializing and deserializing entities. The EntityCacheState is a serializable snapshot of the EntityManager's cache, or of a selected subset of entities. The EntityCacheState can be serialized in text or binary format, and can be transmitted across tiers via a remote server method. Separate the model project The tutorials instruct you to add your entity model to one of the projects generated from a DevForce template. They do so because they are tutorials. The entity model tends to be in its own project/assembly in production applications. This topic demonstrates how to break out the entity model into its own assembly.
The Silverlight and WPF tutorials encourage you to add your entity model to one of the existing projects: the Web Application project in Silverlight examples; the lone application project in the 2-tier WPF example. As your application evolves and begins to add other projects that need to reference the entity model, you may wish you had put the entity model in its own project. You don't have to start over; it's pretty easy to extract it as its own project and re-link the projects that depend upon it. Here you will find step-by-step instructions for Silverlight and non-Silverlight applications. The Silverlight case is the primary scenario as it requires attention to the linking between full .NET and Silverlight assemblies. You can follow along with the zip file attached to this page which contains both "before" and "after" C# Silverlight solutions. Be sure to backup the entire solution before you begin. Add a Model project The new entity model project is a full .NET class library. Starting in Visual Studio 2010: From the menu: File | Add | New Project... Select the Class Library template Ensure the top-left ComboBox says ".NET Framework 4" Name it to match the entity model's namespace; we'll call ours "DomainModel" Delete Class1 In the multi-tier scenarios, the entity model tends to be defined in the web application project; this is always true for Silverlight and also for n-tier WPF demonstrations. The directions are the essentially same for a single-project, 2-tier application wherein the entity model is defined within the lone application project. Follow the instructions here, moving the model out of that project as one would move it from the web application project. Move the Entity Framework model (.edmx), the .tt file, and all custom model classes into DomainModel Select each model-file (you can do all at once with ctrl-mouse-click) Drag them to DomainModel Delete these files from the web application project ( reminder: back-up the solution first ) Add a DomainModel project reference to the web application project Delete model file links in the Silverlight application project This section only applies to Silverlight applications built using DevForce 6.0.9 and earlier. The links to your entity model are now broken and likely display the yellow warning triangle icon. Remove all links to the entity model class files ( Delete the now empty Shared Code folder (if you had one) You must delete those links before proceeding. Finish the Model project You should have a .tt file and a DevForce code-generated file under that .tt file. You probably also have a code-generated file appears under the .edmx file, that's the Entity Framework getting involved when it should not. Open the "Properties" window for the .edmx file by typing Alt-Enter Notice that the "Custom Tool" entry says EntityModelCodeGenerator. o Clear the "Custom Tool" entry (it should be empty). o The Entity Framework generated code file disappears Right-mouse-click the .tt file Pick "Run Custom Tool" This regenerates the code file under the DomainModel namespace and adds the minimally required references at the same time: IdeaBlade.Core IdeaBlade.EntityModel IdeaBlade.Validation .NET's System.ComponentModel.DataAnnotations Make sure you moved over all of the custom model files that you wrote (e.g., custom entity class files and metadata files). Fix Model namespace references The re-located, re-generated entity model classes are now defined in the default namespace for the model project, DomainModel. That is typically what you want. It also means that your model namespace references elsewhere are stranded under the old name. You have to clean them up. Start with the custom classes you moved into the DomainModel project. A Customer partial class, for example, is still defined under its old namespace. Use search-and-replace to swap in the new namespace for all of the custom classes in this project only. Do not search-and-replace the namespace in other projects, especially not your application project. The old namespace name remains legitimate for non-model classes. You are likely to succeed simply by adding "using DomainModel;" statements to the class files that need them. Let the compiler be your guide. Be patient for now. How to postpone namespace update You can postpone this exercise by resetting the default namespace for the DomainModel project to the old namespace name as it was before we moved these files. That namespace is probably the default namespace of the Web Application project. Open the properties editor for the DomainModel project On the "Application" tab, set the "Default namespace" to the previous model namespace value Close that tab Right-mouse-click the .tt file Pick "Run Custom Tool" The entity class file is re-generated again, restoring the entity classes to their old namespace. You can come back and fix this later. The longer you wait, the more you'll have to clean up. Build the Model project Build the DomainModel project. It should build cleanly or be easy to fix as it has no dependencies on other projects in your solution. You may need to add one more DevForce library if you are referencing it in any custom class. IdeaBlade.Linq (only if needed) Select that reference and open its "Properties Window" Set the "Specific Version" to False If this is a non-Silverlight application, you're almost done. Skip ahead to the "Finish the Non- Silverlight Application" section. Add a Silverlight Model project This section applies only to Silverlight applications. Non-Silverlight applications simply reference the DomainModel project. In this topic, our assumption is that you want a separate Silverlight application project to parallel the separate full .NET DomainModel project we just created. From the menu: File | Add | New Project... Select the Silverlight Class Library template Name it to match the entity model's namespace with a .SL suffix; we'll call ours "DomainModel.SL" o Choose the "Silverlight 4" version if asked. Delete Class1 Press Alt-Enter on the new project to open its Properties On the "Silverlight" tab set the "Default namespace:" to DomainModel (i.e., remove the ".SL" suffix. Now you're ready to link to the entity model class files in the DomainModel project. Press Alt-Shift-A to launch the "Add Existing Item" dialog Navigate to the DomainModel project that holds the full .NET model files Select every file code file (.cs or .vb); do not select any other file type. Do not click the "Add" button Drop down the ComboBox next to the "Add" button and pick "Add As Link".
Add Silverlight assembly references: Add DevForce references o IdeaBlade.Core o IdeaBlade.EntityModel o IdeaBlade.Validation o IdeaBlade.Linq (if needed) Select all of them and open the "Properties Window" Set their "Specific Version" to False Add .NET references o System.ComponentModel.DataAnnotations o System.Runtime.Serialization Build the DomainModel.SL project. It should build cleanly or be easy to fix as it has no dependencies on other projects in your solution. Finish the Silverlight Application Almost there! Add a reference to the DomainModel.SL project. Build the entire solution, correcting as necessary. Run. The most likely compile errors will be missing references to your re-factored model project. The entity classes have a new namespace. Adding some "using DomainModel;" statements to the class files that need them should do the trick. Finish the Non-Silverlight Application Almost there! Add a reference to the DomainModel project. Build the entire solution, correcting as necessary. Run. The most likely compile errors will be missing references to your re-factored model project. The entity classes have a new namespace. Adding some "using DomainModel;" statements to the class files that need them should do the trick.
Query using LINQ The most common and flexible method of composing a query is to use LINQ syntax with the EntityQuery.
DevForce LINQ is a comprehensive implementation with unique capabilities: can execute synchronously and asynchronously. applies to remote data source, local cache, or both simultaneously. works in 2-tier mode when the client has line-of-sight access to the database or in n-tier mode when a remote server mediates between clients and the database. can be composed statically inline with other code or dynamically to accommodate user- defined query criteria that can only be known at runtime. Every valid EntityFramework LINQ query is also a valid DevForce LINQ query. The range and power of DevForce LINQ querying may best be appreciated by taking a tour of the EntityFramework's MSDN "101 LINQ Samples" web page. Every sample works in DevForce, 2-tier or n-tier, whether sent to the database or applied to local cache. Every EntityFramework entity type can be queried with DevForce LINQ: all forms of inheritance, complex types, anonymous types, all association cardinalities (including many- to-many). The DevForce LINQ query story begins with EntityQuery. The EntityQuery DevForce provides the EntityQuery<T> class to support LINQ syntax. The EntityQuery<T> implements .NET's IQueryable<T> interface and offers a complete implementation of LINQ functionality, including multiple overloads for all of the following standard LINQ operators: All, Any, Average, Cast, Concat, Contains, Count, DefaultIfEmpty, Distinct, ElementAt, Except, First, FirstOrDefault, GroupBy, Join, Last, LastOrDefault, OfType, OrderBy, OrderByDescending, Select, SelectMany, Single, SingleOrDefault, Skip, SkipWhile, Take, TakeWhile, ThenBy, ThenByDescending, Sum, Union and Where. Because an EntityQuery is most commonly used to query an Entity Data Model, DevForce is subject to the same restrictions which the Entity Framework places on such queries. For more information, see Supported and Unsupported LINQ Methods (LINQ to Entities). Note that while an EntityQuery may be restricted from running against the Entity Framework on the backend, the same query can always be executed locally against the EntityManager's cache regardless of any backend restrictions. Similarly, POCO queries, are also not subject to these restrictions. This is why some of the operators listed above, such as ElementAt, Last, SkipWhile, and TakeWhile, among others, are still provided, even though they cannot be handled by the Entity Framework. In practice, these restrictions tend to be a minimal hindrance because there is usually another method or overload that can accomplish the same result. Several additional DevForce-specific operators are offered as well, such as FirstOrNullEntity and support for building untyped LINQ queries dynamically. EntityQuery basics Entity queries, like all LINQ queries, can be composed, executed and enumerated in a variety of stepwise ways. Consider, for example, the following query: C# var customersQuery = from cust in manager.Customers where cust.ContactTitle == "Sales Representative" orderby cust.CompanyName select cust; VB Dim customersQuery = From cust In manager.Customers Where cust.ContactTitle = "Sales Representative" Order By cust.CompanyName Select cust The same query can also be written using LINQ method-based syntax as shown below: C# var customersQuery = manager.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName) VB Dim customersQuery = manager.Customers _ .Where(Function(c) c.ContactTitle = "Sales Representative") _ .OrderBy(Function(c) c.CompanyName) Whether to use query or method syntax is your choice. Both syntaxes provide the same functionality, with minor differences. We generally use method, sometimes called fluent, syntax in our samples and snippets, but that's because it's the syntax we're most comfortable with. If you're a VB developer, query syntax can be much easier to write and read. The above returns an EntityQuery<Customer>. It's often easier when working with generic types (especially nested generic types), to use an implicitly typed variable, such as shown above. This isn't required, and you can still use explicit type variables too. Creating a query You may have wondered in looking at the above samples what manager.Customers referred to, and why you could append LINQ methods to it. When DevForce generates the code for your entity model it includes these helper properties on the EntityManager. Here's what Customers looks like: C# public IbEm.EntityQuery<Customer> Customers { get { return new IbEm.EntityQuery<Customer>("Customers", this); } } VB Public ReadOnly Property Customers() As IbEm.EntityQuery(Of Customer) Get return new IbEm.EntityQuery(Of Customer)("Customers", Me) End Get End Property DevForce does this to make it easy to compose more complex LINQ queries without having to explicitly construct the EntityQuery from scratch every time. You're not limited to using these helper properties, but most developers find them useful. Basic tasks The customersQuery we showed above may look a bit daunting if you're new to LINQ. Queries can be as simple or complex as you need. Here are a few more samples of simple common queries. Get all entities of a type When you need to retrieve all instances of a type, all Employees for example, you need only provide a simple EntityQuery without restriction or selection methods: C# var query = manager.Employees; VB Dim query = manager.Employees Or in query syntax: C# var query = from emp in manager.Employees select emp; VB Dim query = From emp In manager.Employees Select emp You can execute the query in any of the ways described below. Remember that if there are potentially many instances of the type it's usually not a good idea to bring all of them into the entity cache. If you have 10,000 products for example, you rarely need them all loaded into memory. Simple property filter You'll often want to retrieve a subset of entities based on filter criteria applied to simple properties of the entity. For example, a query to retrieve all customers in the UK: C# var ukQuery = from cust in manager.Customers where cust.Country == "UK" select cust; VB Dim ukQuery = From cust In manager.Customers Where cust.Country = "UK" Select cust Query execution As mentioned earlier, the DevForce EntityQuery<T> implements the IQueryable interface, which means that the execution of the query is deferred until one of the following operations is performed on the query ToList is called on the query. The query is enumerated in a foreach statement. One of the EntityManager ExecuteQuery or ExecuteQueryAsync method overloads is called for the query. An immediate execution method is called on the query. These methods include First, Single, Count along with several others. The AsScalarAsync() method is called followed by a call to an immediate execution method. Note that Silverlight applications, because of their asynchronous nature, can only make use of two of these mechanisms: Calling one of the ExecuteQueryAsync method overloads. Calling the AsScalarAsync() method on the query followed by a call to an immediate execution method. In the example below the addition of a call to ToList() forces DevForce to execute the query immediately: C# List<Customer> customersList = manager.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName) .ToList(); VB Dim customersList As List(Of Customer) = manager.Customers _ .Where(Function(c) c.ContactTitle = "Sales Representative") _ .OrderBy(Function(c) c.CompanyName) _ .ToList() As does the FirstOrNullEntity call in the example below. C# Customer firstCustomer = manager.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName) .FirstOrNullEntity(); VB Dim firstCustomer As Customer = manager.Customers _ .Where(Function(c) c.ContactTitle = "Sales Representative") _ .OrderBy(Function(c) c.CompanyName) _ .FirstOrNullEntity() The same queries executed asynchronously, as would be required in Silverlight, would look like this. C# var query = manager.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName);
query.ExecuteAsync( Sub(op As EntityQueryOperation(Of Customer)) Dim customers As IEnumerable(Of Customer) = op.Results End Sub) and C# var query = manager.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName);
query.AsScalarAsync().FirstOrNullEntity( Sub(op As EntityScalarQueryOperation(Of Customer)) Dim cust As Customer = op.Result End Sub) In the above you may have noticed that instead of calling either the ExecuteQuery or ExecuteQueryAsync methods on the EntityManager, we called execute methods directly on the query itself. Query extension methods such as Execute and ExecuteAsync give you additional flexibility in how you execute a query. These are usually equivalent to the EntityManager methods, except with one important distinction. If a query is created without an EntityManager and executed with a query extension method, then that query will use the DefaultManager. You can find more information on asynchronous queries here. Logging & debugging The DevForce debug log can be used to see more information regarding which queries are executed and when. Information about every query sent to the EntityServer will be written to the debug log. You can also log the generated SQL for queries using the shouldLogSqlQueries attribute on the logging element in your config file. Return part of an entity One common issue when working with entities is how to return part of an entity when only some of the properties of an entity or collection of entities are needed.
Returning part of an entity, or a "slice", can be useful if an entity type has many properties, some of which are large (e.g, BLOBS), and a query could return too much data or take too long to load. Perhaps you are presenting a list of such entities. You won't edit them. If you do edit them, you will use the list as a reference for identifying entities to edit (e.g., by user selection); then you will launch a separate editor and load it with the full entity version retrieved from the database expressly for this purpose. Slice projections are appropriate in such high volume, "read-only" scenarios where the savings are measurable and significant. In most cases, though, entities should still be the primary tool for building any application that requires the update of a domain model's data. There are two ways to select only part of an entity - using anonymous projections, and projections into a custom type. We'll describe both below. Anonymous projections DevForce supports all of the standard LINQ mechanisms for projection of an anonymous type from any query. For example, in the snippet below we build a LINQ query that projects out three properties from the Employee entity type: C# var query = manager.Employees .Select(emp => new { emp.EmployeeID, emp.FirstName, emp.LastName }); VB Dim query = manager.Employees _ .Select(Function(emp) New With { emp.EmployeeID, emp.FirstName, emp.LastName })
Let's look at query syntax too, since it's easier to use in VB: C# var query = from emp in manager.Employees select new {emp.EmployeeID, emp.FirstName, emp.LastName}; VB Dim query = From emp In manager.Employees Select New With {emp.EmployeeID, emp.FirstName, emp.LastName} When the query is executed, an IEnumerable of anonymous types is returned. Here's where implicit type names come in handy, since the anonymous type is not statically defined: C# var anonItems = query.ToList(); foreach (var anonItem in anonItems) { int id = anonItem.EmployeeID; String fName = anonItem.FirstName; String lName = anonItem.LastName; } VB Dim anonItems = query.ToList() For Each anonItem In anonItems Dim id As Integer = anonItem.EmployeeID Dim fName As String = anonItem.FirstName Dim lName As String = anonItem.LastName Next anonItem It's worth pointing out that anonymous projection queries can be sent to the EntityServer, like any other query type. We can issue synchronous or asynchronous queries with anonymous projections too. C# query.ExecuteAsync(op => { foreach (var anonItem in op.Results) { int id = anonItem.EmployeeID; String fName = anonItem.FirstName; String lName = anonItem.LastName; } }); VB query.ExecuteAsync(Sub(op) For Each anonItem In op.Results Dim id As Integer = anonItem.EmployeeID Dim fName As String = anonItem.FirstName Dim lName As String = anonItem.LastName Next anonItem End Sub) We can also do aggregates and calculations within the projections. For example: C# var query = manager.Customers .Select(c => new { c.CustomerID, TotalFreightWithDiscount = c.Orders.Sum(os => os.Freight * .85m) }); VB Dim query = manager.Customers _ .Select(Function(c) New With {Key c.CustomerID, Key .TotalFreightWithDiscount = _ c.Orders.Sum(Function(os) os.Freight *.85D)}) Grouping operations are also supported, as shown in the asynchronous example below. C# var query = manager.Customers .Where(c => c.CompanyName.StartsWith("C")) .GroupBy(c => c.Country) .Select(g => new { Country = g.Key, Count = g.Count() }); query.ExecuteAsync(op => { foreach (var item in op.Results) { string country = item.Country; int count = item.Count; } }); VB Dim query = manager.Customers.Where(Function(c) c.CompanyName.StartsWith("C")) _ .GroupBy(Function(c) c.Country).Select(Function(g) New With _ {Key .Country = g.Key, Key .Count = g.Count()}) query.ExecuteAsync(Sub(op) For Each item In op.Results Dim country As String = item.Country Dim count As Integer = item.Count Next item End Sub) You can also select related entities and lists. This is useful too if a filter needs to be applied to the related entities. C# var anonItems = manager.Customers.Select(c => new { c.CompanyName, c.OrderSummaries }).ToList(); VB Dim anonItems = manager.Customers.Select(Function(c) New With { c.CompanyName, c.OrderSummaries }).ToList() Anonymous types are not public types Notice that the anonymous type returned by these queries is not public. Anonymous types have internal (Friend in VB) visibility. Because of this visibility, you can't directly bind to an anonymous type in Silverlight. Fortunately, DevForce offers the DynamicTypeConverter helper class that can convert a non-public anonymous query result into a public type suitable for binding. C# var anonItems = manager.Customers.Select(c => new { c.CompanyName, c.OrderSummaries }).ToList(); var bindableResult = IdeaBlade.Core.DynamicTypeConverter.Convert(anonItems); VB Dim anonItems = manager.Customers.Select(Function(c) New With _ {Key c.CompanyName, Key c.OrderSummaries}).ToList() Dim bindableResult = IdeaBlade.Core.DynamicTypeConverter.Convert(anonItems) DevForce also offers a helper method which allows you to test whether a query returned an anonymous type, AnonymousFns.IsAnonymousType . Limitations The "slice" projection described here returns an anonymous type, not an entity. The returned object will not enter the entity cache. The EntityManager does not "remember" the query and will send the query to the server every time (assuming that a server visit is consistent with the query strategy and the present state of the EntityManager itself). Remember that the projected result is not an entity. Even if you could make changes, you couldn't save those changes ... not in the way you would save entities. You will also be responsible for coordinating projected values with changes to the source entities. When you display a list of Employees and change one of them, the displayed employee list updates to reflect those changes. Change "Nancy" to "Sally" and it changes in the list too. Not so if the list contains projected employee data - which is disconcerting to users. You can coordinate with the list yourself at the cost of added complexity, maintenance, and testing. Finally, the anonymous types returned by slice projections are difficult to work with. You can't edit the anonymous result. You can't bind directly to the anonymous type (without the assistance of the DyanmicTypeConverter). In Silverlight you can't access type members outside the assembly. You may want to transfer the result into a locally defined type to make it easier to work with. If you expect the projection to have long term, widespread use, you should consider "table splitting" - an Entity Framework modeling technique which defines two (or more entities) mapped to the same table. Or you may prefer to define a serializable type that matches the expected shape and define your projection directly into that, which we'll describe next. Project into a custom type Instead of projecting part of an entity into an anonymous type you have the option of projecting into a custom type which you've defined. This option can be useful since it alleviates the difficulties in working with anonymous types. It does, however, require additional setup. You must first define the custom type. It must be serializable, a known type, and available on both client and server. Here's a simple example, using the Employee slice we used above. C# [DataContract] public class EmployeeSlice : IKnownType {
// Employee properties [DataMember] public int EmployeeID { get; set; } [DataMember] public string FirstName { get; set; } [DataMember] public string LastName { get; set; } [DataMember] public int OrderCount { get; set; }
// Calculated property public string FullName { get { return FirstName + " " + LastName; } } } VB <DataContract> Public Class EmployeeSlice Implements IKnownType
' Employee properties <DataMember> Public Property EmployeeID() As Integer <DataMember> Public Property FirstName() As String <DataMember> Public Property LastName() As String <DataMember> Public Property OrderCount() As Integer
' Calculated property Public ReadOnly Property FullName() As String Get Return FirstName & " " & LastName End Get End Property End Class Note the markup with the DataContract and DataMember attributes - these allow instances of this class to be moved between client and server tiers. Next note the implementation of the DevForce marker interface IKnownType. DevForce probes for this interface during its discovery of known types, and this ensures that DevForce is "aware" that the type may be used in queries. Also note that the data member properties are public. This isn't strictly required, but avoids serialization issues. In Silverlight applications you can define internal setters with a bit of extra work. You'll want to define this class on both client and server. Why? Because your query will be created on the client and executed on the server. Both sides must know about it. How do you define the class on both tiers? The easiest approach, and the one DevForce uses with the generated entity model, is to create the class file in a "server-side" project and link to the file (or files) from the "client-side" project. If the project assembly will be deployed to both tiers, then linking the files is of course not necessary, but you will need to ensure the assembly is deployed to both tiers. You'll also want to ensure the class can be discovered by DevForce. If you've modified the discovery options, be sure that the assembly containing this type will be included. The query is only slightly different than the anonymous projection query. The "Select" clause features a "new" keyword followed by our receiving type, "EmployeeSlice". The projection is no longer anonymous. C# var query = manager.Employees .Select(emp => new EmployeeSlice { EmployeeID = emp.EmployeeID, FirstName = emp.FirstName, LastName = emp.LastName, OrderCount = emp.Orders.Count(), }); VB Dim query = manager.Employees _ .Select(Function(emp) New EmployeeSlice With { .EmployeeID = emp.EmployeeID, .FirstName = emp.FirstName, .LastName = emp.LastName, .OrderCount = emp.Orders.Count() }) Use with POCO types Although we are projecting into a known type, that type is not an entity. The query is not delivering entities. The result objects do not enter the entity cache. The EntityManager does not remember the query and will re-send it to the server with every execution unless otherwise barred from doing so. And you cannot save instances of this type to the database. If you must store this data in the entity cache you can work around these limitations by assigning a Key attribute to one or more properties of the type. If instances of the type have a unique identity then they will be stored in the entity cache when queried, and can be manipulated and saved as POCO types. The query itself won't be placed in the query cache. Include related entities An EntityManager query always returns entities of a single type, the return type identified in the query object. But what about related entities within the same query such as entities related to the returned entities? When do we get those? In general, the answer is that you do not get any related entities until you actually need them. The queries that are executed to retrieve these entities are called "lazy queries". However, it is often very useful, both for simplicity of code as well as for performance reasons, to "prefetch" or "eagerly load" data that you do not immediately need, but that you have a good reason to believe will be needed very soon. These are called "eager queries".
Eager queries and the Include method Eager queries are performed using the Include method. The idea behind this method is that after a query is performed, you sometimes want to retrieve many of the entities directly related to the entities being returned. You can accomplish this by specifying the "property paths" to these entities from the returned entities. For example: C# var query = anEntityManager.Customers.Where(c =>c.CompanyName.StartsWith{"A")).Include("Orders"); VB Dim query = anEntityManager.Customers.Where(c =>c.CompanyName.StartsWith{"A")).Include("Orders") In this case, the "property path" between Customers and Orders was very simple; the Customer type has an Orders property and it is the name of this property that we are using in the Include method call. This query will return all of the customers with company names starting with "A" and will also preload the EntityManager's cache with all of their related orders. More complex includes are also possible: C# var query = anEntityManager.Customers .Where(c =>c.CompanyName.StartsWith{"A")) .Include("Orders.OrderDetails.Product") .Include("Orders.SalesRep") VB Dim query = anEntityManager.Customers _ .Where(c =>c.CompanyName.StartsWith{"A")) _ .Include("Orders.OrderDetails.Product") _ .Include("Orders.SalesRep") In this case, the Order type has an "OrderDetails" property and a "SalesRep" property, and the OrderDetail type has a "Product" property. Note that the property paths may not be valid navigation paths. i.e. you couldn't actually execute anOrder.OrderDetails.Product because the OrderDetails property returns a collection and it is the instances of this collection and not the collection itself that has a Product property. In the preceding examples we have added the Include methods to the end of the query chain, but we would get exactly the same results if we were to rewrite the query as follows: C# var query = anEntityManager.Customers .Include("Orders.OrderDetails.Product") .Include("Orders.SalesRep") .Where(c =>c.CompanyName.StartsWith{"A")) VB Dim query = anEntityManager.Customers _ .Include("Orders.OrderDetails.Product") _ .Include("Orders.SalesRep") _ .Where(c =>c.CompanyName.StartsWith{"A")) Important points Includes always operate on the 'final' result type of a query, regardless of where they appear in the query chain. When using an Include like "Orders.OrderDetails.Product" with multiple "parts" to the property path, each of the intermediate results will also be added to the EntityManager's entity cache. Note that the use of the Include method doesnt change the list of entities returned from the query. The caller still receives the same results with or without the use of an Include method. The difference is that before returning these results, the "Include" processing fetches the related entities and merges them into the cache. This occurs behind the scenes and does not effect the query result. When using an Include such as "Orders" for a Customer, the Include retrieves all orders of each selected Customer. You cannot filter the "Included" orders. If you need to do that, consider a projection query. Includes in Silverlight In Silverlight apps, where all data retrieval must be asynchronous, the benefits of preloading data ("eager querying") are even more general. In the following snippet, we preload, using Include method calls, a large object graph a group of Customers that meet a specified condition. Having done so, all of our subsequent queries for entities can be cache-only and synchronous: C# public void PreloadCustomerOrderData() { IEntityQuery<Customer> query = anEntityManager.Customers.Where(c => c.Country == "France") .Include("Orders.OrderDetails.Product.Supplier") .Include("Orders.SalesRep"); _em1.ExecuteQueryAsync(query, GotCustomers); }
private void GotCustomers(EntityQueryOperation<Customer> args) { // all of the following queries can now execute synchronously and will return the 'related' entities from the query above. var customers = anEntityManager.Customers.With(QueryStrategy.CacheOnly).ToList(); var employees = anEntityManager.Employees.With(QueryStrategy.CacheOnly).ToList(); var orders = anEntityManager.Orders.With(QueryStrategy.CacheOnly).ToList(); var orderDetails = anEntityManager.OrderDetails.With(QueryStrategy.CacheOnly).ToList(); var products = anEntityManager.Products.With(QueryStrategy.CacheOnly).ToList(); var suppliers = anEntityManager.Suppliers.With(QueryStrategy.CacheOnly).ToList(); } VB Public Sub PreloadCustomerOrderData() Dim query As IEntityQuery(Of Customer) = anEntityManager.Customers.Where(Function(c) c.Country = "France") _ .Include("Orders.OrderDetails.Product.Supplier") _ .Include("Orders.SalesRep") _em1.ExecuteQueryAsync(query, AddressOf GotCustomers) End Sub
Private Sub GotCustomers(ByVal args As EntityQueryOperation(Of Customer)) ' all of the following queries can now execute synchronously and ' will return the 'related' entities from the query above. Dim customers = anEntityManager.Customers.With(QueryStrategy.CacheOnly).ToList() Dim employees = anEntityManager.Employees.With(QueryStrategy.CacheOnly).ToList() Dim orders = anEntityManager.Orders.With(QueryStrategy.CacheOnly).ToList() Dim orderDetails = anEntityManager.OrderDetails.With(QueryStrategy.CacheOnly).ToList() Dim products = anEntityManager.Products.With(QueryStrategy.CacheOnly).ToList() Dim suppliers = anEntityManager.Suppliers.With(QueryStrategy.CacheOnly).ToList() End Sub Performance details While use of the Include syntax greatly reduces the number of queries submitted to EntityServer, it is also likely to change the number of queries performed by the backend database. This is because a single query with an Include will typically perform one or two database queries depending on the complexity of the query, but the same query without an include would result in an initial single query followed by many additional queries spaced out over time as each individual client side property navigation from the resulting entities caused a further "lazy" query evaluation. Each of these "lazy evaluations" necessitates a separate trip to both the EntityServer and then to the database. In an n-tier deployment using the EntityServer, these "lazy evaluations" can end up being very expensive because of the latency of the network. But be careful! You could be trying to get too much data at one time. You could be joining data from too many tables at one time. Consequently, the query might perform more poorly than if you had made several trips. Or it might just collapse of its own weight. Make sure you test your queries. "Eager queries" are great, but we have also seen them being badly misused. The preceding paragraphs have enumerated the advantages, but there is sometimes a tendency to think that because you might need some related entities that you should always "preload" them. This can be a bad idea if most of the time this data is not needed, and you are still paying the cost of retrieving and transmitting them. 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. Strongly-typed Includes Because the Include syntax uses strings that represent "property paths", it is possible that these paths may be incorrect and that the problem is not discovered until runtime. A type-safe alternative exists that can be used for all "simple" property paths. A "simple property path" is one that can be expressed by a simple lambda expression, so "Order.OrderDetails" is a simple property path but "Order.OrderDetails.Product" is not. The type-safe alternative is to use the PathFor method that is generated into every DevForce entity type. C# var query1 = anEntityManager.Customers .Where(c =>c.CompanyName.StartsWith{"A")) .Include(Customer.PathFor( c => c.Orders)); // instead of .Include("Orders");
var query2 = anEntityManager.OrderDetails .Include(OrderDetail.PathFor( od => od.Product.Suppliers)); // instead of "Product.Suppliers" VB Dim query1 = anEntityManager.Customers.Where(Function(c) _ c.CompanyName.StartsWith{"A")).Include(Customer.PathFor(Function(c) c.Orders)) _ ' instead of.Include("Orders");
Dim query2 = anEntityManager.OrderDetails.Include(OrderDetail. _ PathFor(Function(od) od.Product.Suppliers)) ' instead of "Product.Suppliers" The advantage of this syntax is that if any of these property names ever change, the compiler will catch the error instead of allowing the code to compile with a "bad" include. Include and filter related entities A common request is to be able to perform an Include operation where the Include is modified by a where clause. While this is not currently possible, there is another approach that accomplishes almost the same thing. The trick is to to use an anonymous projection.
The workaround
Let say we wanted to write a query that looked something like the following. Note that this code will NOT compile. C# var query = anEntityManager.Products.Where(p =>p.Category.Name == "Books") .(Include("OrderDetails").Where(od => od.Quantity > 5)) VB Dim query = anEntityManager.Products.Where(Function(p) p.Category.Name = "Books") _ .(Include("OrderDetails").Where(Function(od) od.Quantity > 5)) What we are try to do is select only those Products that are books, and then only Include those OrderDetails, for these books, where the OrderDetail's Quantity is greater than 5. The syntax above does NOT work, but we can do something very similar, like this: C# var query = anEntityManager.Products.Where(p => p.Category.Name == "Books") .Select(p => new { Product = p, OrderDetails = p.OrderDetails.Where(od => od.Quantity > 5) }); var results = query.ToList(); var products = results.Select( x => x.Product); VB Dim query = anEntityManager.Products.Where(Function(p) p.Category.Name = _ "Books").Select(Function(p) New With {Key .Product = p, Key .OrderDetails = _ p.OrderDetails.Where(Function(od) od.Quantity > 5)}) Dim results = query.ToList() Dim products = results.Select(Function(x) x.Product) The idea here is in DevForce any query that returns entities causes those entities to be added to the EntityManager's entity cache. This is basically, what the Include extension method does as well. It bring entities down and "includes" them in the cache, but they are not part of the "result" of the query. In the example above we actually return an anonymous type that "includes" both Products and OrderDetails, so both types of entities are added to the cache. But we don't necessarily want an anonymous result, what we possibly want is just the list of Products. Hence, the line after the ToList() call in the example above. In that line we project out just the "Products" that we just queried. Paging queries Last modified on March 29, 2011 17:06 Paging a query in DevForce is performed via the use of the LINQ Take and Skip operators. For example, if we think of a page size as being 50 entities, then we can fetch any page n of Customers, using the following kind of query logic. C# var pageNumber = 1; var pageSize = 50; var pagedQuery = _em1.Customers.Skip((pageNumber-1)*pageSize).Take(pageSize);
var customersOnFirstPage = pagedQuery.ToList();
pageNumber++ var customersOnNextPage = pagedQuery.ToList();
pageNumber = 17; var customersOnPage17 = pagedQuery.ToList(); VB Dim pageNumber = 1 Dim pageSize = 50 Dim pagedQuery = _em1.Customers.Skip((pageNumber - 1) * pageSize).Take(pageSize)
Dim customersOnFirstPage = pagedQuery.ToList()
pageNumber = pageNumber + 1 Dim customersOnNextPage = pagedQuery.ToList()
pageNumber = 17 Dim customersOnPage17 = pagedQuery.ToList() For Silverlight display purposes DevForce provides a class known as the EntityQueryPagedCollectionView that internally does something very similar. A code sample is available. Pass parameters to a query Last modified on March 23, 2011 22:44 Contents Closures We often want to create a query where we can pass parameters to a query to control some aspect of the query. For database developers there is often a temptation to look for a query parameters collection on the query to accomplish this task. While such a collection does exist, it is ONLY intended for use with a very specific subset of POCO queries. In all other cases, including most POCO query use cases, the alternative provided below is both simpler and more easily understood. Closures Parameters can be easily simulated by using .NET closures. This is simply a complicated way of saying that we can just include a reference to a local variable in our query and the query will use the value of this variable when the query is executed. For example, in the query below, we create a single query object but change the parameter customerName between executions of this query. C# String customerName = null; // The customerName variable is a 'parameter' var query = myEntityManager.Customers.Where(c => c.CompanyName.StartsWith(customerName)); customerName = "ABC"; var customersStartingWithABC = query.ToList(); customerName = "XYZ"; var customersStartingWithXYZ = query.ToList(); VB Dim customerName As String = Nothing ' The customerName variable is a 'parameter' Dim query = myEntityManager.Customers.Where(Function(c) c.CompanyName.StartsWith(customerName)) customerName = "ABC" Dim customersStartingWithABC = query.ToList() customerName = "XYZ" Dim customersStartingWithXYZ = query.ToList() Basically a variable can be used anywhere a constant value would normally be used in a query. The value of the variable will be evaluated when the query is executed, not when the query is composed. Another example is shown below: C# float minDiscount = 0; var query2 = _em1.OrderSummaries.Where(o => o.OrderDetails.Any(od => od.Discount > minDiscount)); minDiscount = 0.0F; var ordersWithAnyDiscount = query2.ToList(); minDiscount = 0.1F; var ordersWithLargeDiscount = query2.ToList(); VB Dim minDiscount As Single = 0 Dim query2 = _em1.OrderSummaries.Where(Function(o) _ o.OrderDetails.Any(Function(od) od.Discount > minDiscount)) minDiscount = 0.0F Dim ordersWithAnyDiscount = query2.ToList() minDiscount = 0.1F Dim ordersWithLargeDiscount = query2.ToList() More complex queries that are composed completely dynamically are discussed in dynamic queries. One interesting side note here is that the QueryCache, will contain the "resolved" version of the query. This means that if the same query is executed twice with two different values provided for any closure "parameters"; two entries will be made in the query cache. For example, the following two queries will be treated identically by the EntityManager. C# customerName = "ABC"; var query1 = myEntityManager.Customers.Where(c => c.CompanyName.StartsWith(customerName)); var customersStartingWithABC = query1.ToList();
var query2 = myEntityManager.Customers.Where(c => c.CompanyName.StartsWith("ABC")); var sameCustomersFromSameQuery = query2.ToList(); VB customerName = "ABC" Dim query1 = myEntityManager.Customers.Where(Function(c) _ c.CompanyName.StartsWith(customerName)) Dim customersStartingWithABC = query1.ToList()
Dim query2 = myEntityManager.Customers.Where(Function(c) _ c.CompanyName.StartsWith("ABC")) Dim sameCustomersFromSameQuery = query2.ToList()
Execute scalar query asynchronously Last modified on April 24, 2011 17:21 Contents The problem AsScalarAsync A scalar immediate execution query is a LINQ query which performs an aggregation (such as Count or Group) or returns only one element (such as First or Single). Because these methods force immediate execution of the query they can't be directly used with asynchronous queries, but using the AsScalarAsync method you can execute scalar immediate execution queries asynchronously.
The problem You've probably noticed something about a query like the following: C# int ct = manager.Customers.Count();
It doesn't return a query object (an EntityQuery<T>) as other queries do. Instead, it returns the count of the items in the entity set. Or consider another example: C# Customer cust = manager.Customers.First();
It too doesn't return a query, but instead the first customer. Both these queries are immediate execution queries in LINQ. They differ from the usual deferred execution queries which allow you to build a query in one step and execute the query at a later time. Immediate execution queries execute, well, immediately, and synchronously; you can't separate the creation of the query from the execution. In an asynchronous environment such as Silverlight, where all queries sent to the EntityServer must be executed asynchronously, immediate execution queries pose a problem. For example, you can't do the following: C# // Will not work! var query = manager.Customers.First(); query.ExecuteAsync(); AsScalarAsync Enter the DevForce AsScalarAsync operator and the EntityScalarAsyncExtensions . It's easiest to understand this with an example. C# var op = manager.Customers.AsScalarAsync().Count(); op.Completed += (o, e) => { int ct = e.Result; };
This looks much the same as our earlier synchronous example, with two important differences. One, AsScalarAsync is called to convert the query to an IEntityScalarQuery<T> before the Count method is called. Second, an EntityScalarQueryOperation<T> operation object is returned. The query has been executed immediately, but asynchronously. As with any asynchronous query you can provide a callback or listen on its Completed event. Like their synchronous counterparts, these methods can also accept a predicate. For example, C# var op = manager.Employees.AsScalarAsync().First(e => e.LastName.StartsWith("D")); op.Completed += (o, e) => { Employee emp = e.Result; }; You can also write more complex queries, such as the one below using an Include: C# var op = manager.Employees.Include("Orders").AsScalarAsync().FirstOrNullEntity(e => e.Id == 1); op.Completed += (o, e) => { Employee emp = e.Result; var orders = emp.Orders; // Will not be pending. }; Here's a query built dynamically: C# var query = EntityQuery.Create(typeof(Customer)); var pd = PredicateBuilder.Make("CompanyName", FilterOperator.StartsWith, "D"); var op = query.Where(pd).AsScalarAsync().First(); op.Completed += (o, e) => { var cust = e.Result; }; The supported immediate execution methods are: All, Any, Average, Contains, Count, First, FirstOrDefault, FirstOrNullEntity, LongCount, Max, Min, Single, SingleOrDefault, SingleOrNullEntity, and Sum. Examples of each are provided in the API documentation .
LINQ query examples Last modified on May 20, 2011 17:38 Our LINQ query examples contain a collection of sample DevForce LINQ queries organized according to the type of operator being demonstrated. These samples are based on the Microsoft Entity Framework Query Samples, which were in turn based on the MSDN "101 LINQ Samples". You'll find all these samples, and more, in the Query Explorer utility, which will allow you to download the code and database to explore LINQ at your own pace. The following pages contain snippets from many of the samples in the Query Explorer to briefly show query topics by operation. Restriction operators - Where, Any, All Examples of the LINQ Where, Any and All operators are shown below. In the examples below _em1 is an EntityManager. C# [Description("This sample uses WHERE to find all customers whose contact title is Sales Representative.")] public void LinqToEntities01() {
var query = from cust in _em1.Customers where cust.ContactTitle == "Sales Representative" orderby cust.CompanyName select cust; var r = query.ToList(); Assert.IsTrue(r.First().CompanyName == "Alfreds Futterkiste"); }
[Description("This sample uses WHERE to find all orders placed before 1997.")] public void LinqToEntities02() { var metadata = EntityMetadataStore.Instance.GetEntityMetadata(typeof(InternationalOrder)); DateTime dt = new DateTime(1997, 1, 1); var query = from order in _em1.Orders where order.OrderDate < dt orderby order.OrderDate select order; Assert.IsTrue(query.First().Freight == 32.38M); }
[Description("This sample uses WHERE to filter for Products that have stock below their reorder level and have a units on] public void LinqToEntities03() { var query = from p in _em1.Products where p.UnitsInStock < p.ReorderLevel && p.UnitsOnOrder == 0 orderby p.UnitsInStock select p; Assert.IsTrue(query.First().ProductName == "Nord-Ost Matjeshering"); }
[Description("This sample uses WHERE to filter out Products that have a UnitPrice less than 10.")] public void LinqToEntities04() { var query = from p in _em1.Products where p.UnitPrice < 10 orderby p.ProductName select p; Assert.IsTrue(query.First().ProductName == "Filo Mix"); }
[Description("This sample uses WHERE to find Employees in London.")] public void LinqToEntities04a() { var query = from e in _em1.Employees where e.Address.City == "London" orderby e.EmployeeID select e; Assert.IsTrue(query.First().LastName == "Buchanan"); }
[Description("This sample uses WHERE to get previous employees.")] public void LinqToEntities05() { var query = from e in _em1.Employees where e is PreviousEmployee orderby e.EmployeeID select e; var r = query.ToList(); Assert.IsTrue(r.First().LastName == "King"); }
[Description("This sample uses WHERE to get employees who handle the Boston territory.")] public void LinqToEntities06() {
var query = from e in _em1.Employees.OfType<CurrentEmployee>() where e.Territories.Any(t => t.TerritoryDescription == "Boston") orderby e.EmployeeID select e; Assert.IsTrue(query.First().LastName == "Fuller"); }
[Description("This sample uses any Customers who placed an order in 1997.")] public void LinqToEntities07() { var query = from c in _em1.Customers where c.Orders.Any(o => o.OrderDate.HasValue == true && o.OrderDate.Value.Year == 1997) select c; Assert.IsTrue(query.Count() == 85); }
[Description("This sample uses ANY to check for any out-of-stock products.")] public void LinqToEntities08() { var query = _em1 .Suppliers .Where(s => s.Products .Any(p => p.UnitsInStock == 0)) .Select(s => s); Assert.IsTrue(query.Count() == 5); }
[Description("This sample uses WHERE and ANY to get orders containing a product with a unit on order.")] public void LinqToEntities09() { var query = from o in _em1.Orders where o.OrderDetails.Any(od => od.Product.UnitsOnOrder > 0) select o; Assert.IsTrue(query.ToList().Count == 366); Assert.IsTrue(query.Count() == 366); }
[Description("This sample uses COUNT to get Products sold to Customers in the same Country " + "as the Products' Suppliers, and where all the Products in the order were from the same Country.")] public void LinqToEntities10() { var query = from p in _em1.Products where p.OrderDetails.Count(od => od.Order.Customer.Address.Country == p.Supplier.Address.Country) > 2 select p; Assert.IsTrue(query.Count() == 20); } VB
<Description("This sample uses WHERE to find all customers whose contact title is Sales Representative.")> Public Sub LinqToEntities01()
Dim query = From cust In _em1.Customers Where cust.ContactTitle = "Sales Representative" Order By cust.CompanyName Select cust Dim r = query.ToList() Assert.IsTrue(r.First().CompanyName = "Alfreds Futterkiste") End Sub
<Description("This sample uses WHERE to find all orders placed before 1997.")> Public Sub LinqToEntities02() Dim metadata = EntityMetadataStore.Instance.GetEntityMetadata(GetType(InternationalOrder)) Dim dt As New Date(1997, 1, 1) Dim query = From order In _em1.Orders Where order.OrderDate < dt Order By order.OrderDate Select order Assert.IsTrue(query.First().Freight = 32.38D) End Sub
<Description("This sample uses WHERE to filter for Products that have stock below their reorder level and have a units on> Public Sub LinqToEntities03() Dim query = From p In _em1.Products Where p.UnitsInStock < p.ReorderLevel AndAlso p.UnitsOnOrder = 0 Order By p.UnitsInStock Select p Assert.IsTrue(query.First().ProductName = "Nord-Ost Matjeshering") End Sub
<Description("This sample uses WHERE to filter out Products that have a UnitPrice less than 10.")> Public Sub LinqToEntities04() Dim query = From p In _em1.Products Where p.UnitPrice < 10 Order By p.ProductName Select p Assert.IsTrue(query.First().ProductName = "Filo Mix") End Sub
<Description("This sample uses WHERE to find Employees in London.")> Public Sub LinqToEntities04a() Dim query = From e In _em1.Employees Where e.Address.City = "London" Order By e.EmployeeID Select e Assert.IsTrue(query.First().LastName = "Buchanan") End Sub
<Description("This sample uses WHERE to get previous employees.")> Public Sub LinqToEntities05() Dim query = From e In _em1.Employees Where TypeOf e Is PreviousEmployee Order By e.EmployeeID Select e Dim r = query.ToList() Assert.IsTrue(r.First().LastName = "King") End Sub
<Description("This sample uses WHERE to get employees who handle the Boston territory.")> Public Sub LinqToEntities06()
Dim query = From e In _em1.Employees.OfType(Of CurrentEmployee)() Where e.Territories.Any(Function(t) t.TerritoryDescription = "Boston") Order By e.EmployeeID Select e Assert.IsTrue(query.First().LastName = "Fuller") End Sub
<Description("This sample uses any Customers who placed an order in 1997.")> Public Sub LinqToEntities07() Dim query = From c In _em1.Customers Where c.Orders.Any(Function(o) o.OrderDate.HasValue = True AndAlso o.OrderDate.Value.Year = 1997) Select c Assert.IsTrue(query.Count() = 85) End Sub
<Description("This sample uses ANY to check for any out-of-stock products.")> Public Sub LinqToEntities08() Dim query = _em1.Suppliers.Where(Function(s) s.Products.Any(Function(p) p.UnitsInStock = 0)).Select(Function(s) s) Assert.IsTrue(query.Count() = 5) End Sub
<Description("This sample uses WHERE and ANY to get orders containing a product with a unit on order.")> Public Sub LinqToEntities09() Dim query = From o In _em1.Orders Where o.OrderDetails.Any(Function(od) od.Product.UnitsOnOrder > 0) Select o Assert.IsTrue(query.ToList().Count = 366) Assert.IsTrue(query.Count() = 366) End Sub
<Description("This sample uses COUNT to get Products sold to Customers in the same Country " & "as the Products' Suppliers, and where all the Products in the order were from the same Country.")> Public Sub LinqToEntities10() Dim query = From p In _em1.Products Where p.OrderDetails.Count(Function(od) od.Order.Customer.Address.Country = p.Supplier.Address.Country) > 2 Select p Assert.IsTrue(query.Count() = 20) End Sub
Projection operators - Select, SelectMany, anonymous projections Examples of the LINQ Select and SelectMany operators are shown below. In the examples below _em1 is an EntityManager. C# [Description("This samples uses SELECT to get all Customers as Entity Objects.")] public void LinqToEntities11() { var query = from c in _em1.Customers select c; Assert.IsTrue(query.Count() == 91); }
[Description("This samples uses SELECT to get all Customer Contact Names as Strings.")] public void LinqToEntities12() { var query = from c in _em1.Customers orderby c.ContactName select c.ContactName; var r = query.ToList(); Assert.IsTrue(r.First() == "Alejandra Camino"); }
[Description("This samples uses SELECT to get all Customer Contact Names as an anonoymous type.")] public void LinqToEntities13() { var query = from c in _em1.Customers orderby c.CompanyName select new { c.ContactName };
var r = query.ToList(); Assert.IsTrue(r.First().ContactName == "Maria Anders"); }
[Description("This sample uses SELECT to get Orders as anonymous type")] public void LinqToEntities14() { var query = from o in _em1.Orders where o.Customer.Address.City == "London" orderby o.OrderDate select new { o };
var r = query.ToList(); Assert.IsTrue(r.Count() == 46); Assert.IsTrue(r.First().o.OrderDate == new DateTime(1996, 8, 26)); }
[Description("This sample uses SELECT to get all Orders and associated Customers as anonymous type")] public void LinqToEntities15() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = from o in _em1.Orders where o.Customer.Address.City == "London" orderby o.Customer.CompanyName descending select new { o, o.Customer };
var r = query.ToList(); Assert.IsTrue(r.Count() == 46); Assert.IsTrue(r.First().Customer.CompanyName == "Seven Seas Imports"); }
[Description("This sample uses SELECTMANY to get all Orders for a Customer as a flat result")] public void LinqToEntities16() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = from c in _em1.Customers where c.CustomerID == "ALFKI" from o in c.Orders orderby o.OrderDate select o;
var r = query.ToList(); Assert.IsTrue(r.Count() == 6); Assert.IsTrue(r.First().OrderDate == new DateTime(1997, 8, 25)); }
[Description("This sample uses SELECTMANY to get all Orders for a Customer as a flat result as a method query")] public void LinqToEntities17() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = _em1.Customers.Where(cust => cust.CustomerID == "ALFKI") .SelectMany(cust => cust.Orders .OrderBy(o => o.OrderDate));
var r = query.ToList(); Assert.IsTrue(r.Count() == 6); Assert.IsTrue(r.First().OrderDate == new DateTime(1997, 8, 25)); }
[Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result")] public void LinqToEntities18() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = from c in _em1.Customers where c.Address.Country == "Denmark" from o in c.Orders orderby o.OrderDate select o;
var r = query.ToList(); Assert.IsTrue(r.Count() == 18); Assert.IsTrue(r.First().OrderDate == new DateTime(1996, 10, 29)); }
[Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result as a method query")] public void LinqToEntities19() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = _em1.Customers.Where(cust => cust.Address.Country == "Denmark") .SelectMany(cust => cust.Orders);
var r = query.ToList(); Assert.IsTrue(r.Count() == 18); }
[Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result")] public void LinqToEntities20x() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = from c in _em1.Customers where c.Address.Country == "Denmark" from o in c.Orders where o.Freight > 5 orderby o.OrderDate select o;
Assert.IsTrue(query.Count() == 17); Assert.IsTrue(query.First().OrderDate == new DateTime(1996, 10, 29)); }
[Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as an anonymous type containing the Orders and Customer flat result")] public void LinqToEntities21x() { _em1.DefaultQueryStrategy = QueryStrategy.Normal.With(QueryInversionMode.Manual); var query = from c in _em1.Customers where c.Address.Country == "Denmark" orderby c.CompanyName from o in c.Orders where o.Freight > 5 orderby o.OrderDate select new { c, o};
Assert.IsTrue(query.Count() == 17); var x = query.First(); Assert.IsTrue(query.First().o.OrderDate == new DateTime(1996, 10, 29)); }
[Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result using LINQ opeartors")] public void LinqToEntities22() { _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; var query = _em1 .Customers .Where(cust => cust.Address.Country == "Denmark") .SelectMany(cust => cust.Orders .Where(o => o.Freight > 5) .OrderBy(o => o.OrderDate)); var r = query.ToList(); Assert.IsTrue(r.Count() == 17); Assert.IsTrue(r.First().OrderDate == new DateTime(1996, 10, 29)); } VB <Description("This samples uses SELECT to get all Customers as Entity Objects.")> Public Sub LinqToEntities11() Dim query = From c In _em1.Customers Select c Assert.IsTrue(query.Count() = 91) End Sub
<Description("This samples uses SELECT to get all Customer Contact Names as Strings.")> Public Sub LinqToEntities12() Dim query = From c In _em1.Customers Order By c.ContactName Select c.ContactName Dim r = query.ToList() Assert.IsTrue(r.First() = "Alejandra Camino") End Sub
<Description("This samples uses SELECT to get all Customer Contact Names as an anonoymous type.")> Public Sub LinqToEntities13() Dim query = From c In _em1.Customers Order By c.CompanyName Select New With {Key c.ContactName}
Dim r = query.ToList() Assert.IsTrue(r.First().ContactName = "Maria Anders") End Sub
<Description("This sample uses SELECT to get Orders as anonymous type")> Public Sub LinqToEntities14() Dim query = From o In _em1.Orders Where o.Customer.Address.City = "London" Order By o.OrderDate Select New With {Key o}
Dim r = query.ToList() Assert.IsTrue(r.Count() = 46) Assert.IsTrue(r.First().o.OrderDate = New Date(1996, 8, 26)) End Sub
<Description("This sample uses SELECT to get all Orders and associated Customers as anonymous type")> Public Sub LinqToEntities15() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = From o In _em1.Orders Where o.Customer.Address.City = "London" Order By o.Customer.CompanyName Descending Select New With {Key o, Key o.Customer}
Dim r = query.ToList() Assert.IsTrue(r.Count() = 46) Assert.IsTrue(r.First().Customer.CompanyName = "Seven Seas Imports") End Sub
<Description("This sample uses SELECTMANY to get all Orders for a Customer as a flat result")> Public Sub LinqToEntities16() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = From c In _em1.Customers Where c.CustomerID = "ALFKI" From o In c.Orders Order By o.OrderDate Select o
Dim r = query.ToList() Assert.IsTrue(r.Count() = 6) Assert.IsTrue(r.First().OrderDate = New Date(1997, 8, 25)) End Sub
<Description("This sample uses SELECTMANY to get all Orders for a Customer as a flat result as a method query")> Public Sub LinqToEntities17() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").SelectMany(Function(cust) cust.Orders.OrderBy(Function(o) o.OrderDate))
Dim r = query.ToList() Assert.IsTrue(r.Count() = 6) Assert.IsTrue(r.First().OrderDate = New Date(1997, 8, 25)) End Sub
<Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result")> Public Sub LinqToEntities18() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = From c In _em1.Customers Where c.Address.Country = "Denmark" From o In c.Orders Order By o.OrderDate Select o
Dim r = query.ToList() Assert.IsTrue(r.Count() = 18) Assert.IsTrue(r.First().OrderDate = New Date(1996, 10, 29)) End Sub
<Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result as a method query")> Public Sub LinqToEntities19() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = _em1.Customers.Where(Function(cust) cust.Address.Country = "Denmark").SelectMany(Function(cust) cust.Orders)
Dim r = query.ToList() Assert.IsTrue(r.Count() = 18) End Sub
<Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result")> Public Sub LinqToEntities20x() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = From c In _em1.Customers Where c.Address.Country = "Denmark" From o In c.Orders Where o.Freight > 5 Order By o.OrderDate Select o
Assert.IsTrue(query.Count() = 17) Assert.IsTrue(query.First().OrderDate = New Date(1996, 10, 29)) End Sub
<Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as an anonymous type containing the Orders and Customer flat result")> Public Sub LinqToEntities21x() _em1.DefaultQueryStrategy = QueryStrategy.Normal.With(QueryInversionMode.Manual) Dim query = From c In _em1.Customers Where c.Address.Country = "Denmark" Order By c.CompanyName From o In c.Orders Where o.Freight > 5 Order By o.OrderDate Select New With {Key c, Key o}
Assert.IsTrue(query.Count() = 17) Dim x = query.First() Assert.IsTrue(query.First().o.OrderDate = New Date(1996, 10, 29)) End Sub
<Description("This sample uses SELECTMANY to get all Orders for Customers in Denmark as a flat result using LINQ opeartors")> Public Sub LinqToEntities22() _em1.DefaultQueryStrategy = QueryStrategy.DataSourceOnly Dim query = _em1.Customers.Where(Function(cust) cust.Address.Country = "Denmark").SelectMany(Function(cust) cust.Orders.Where(Function(o) o.Freight > 5).OrderBy(Function(o) o.OrderDate)) Dim r = query.ToList() Assert.IsTrue(r.Count() = 17) Assert.IsTrue(r.First().OrderDate = New Date(1996, 10, 29)) End Sub
Ordering and Grouping operators - OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy Examples of the LINQ OrderBy, OrderByDescending, ThenBy, ThenByDescending and GroupBy are shown below. In the examples below _em1 is an EntityManager. C# [Description("Select all customers ordered by ContactName.")] public void LinqToEntities53() { var query = from c in _em1.Customers orderby c.ContactName select c; Assert.IsTrue(query.First().ContactName == "Alejandra Camino"); }
[Description("Select all customers ordered by ContactName descending.")] public void LinqToEntities54() { var query = from c in _em1.Customers orderby c.CompanyName descending select c; Assert.IsTrue(query.First().CompanyName == "Wolski Zajazd"); }
[Description("Select an anonoymous type with all product IDs ordered by UnitsInStock.")] public void LinqToEntities55() { var query = from p in _em1.Products orderby p.UnitsInStock select new { p.ProductID, p.UnitsInStock }; Assert.IsTrue(query.First().ProductID == 5); }
[Description("Select an anonoymous type with all product IDs ordered by UnitsInStock as a method query.")] public void LinqToEntities56() { var query = _em1.Products.OrderBy(p => p.UnitsInStock) .Select(p2 => new { p2.ProductID, p2.UnitsInStock }); Assert.IsTrue(query.First().ProductID == 5); Assert.IsTrue(query.First().UnitsInStock == 0); }
[Description("Select all customers ordered by the descending region.")] public void LinqToEntities57() { var query = from c in _em1.Customers orderby c.Address.Region descending select c; Assert.IsTrue(query.First().CustomerID == "SPLIR"); Assert.IsTrue(query.First().Address.Region == "WY"); }
[Description("Select all customers ordered by the descending region as a method query.")] public void LinqToEntities58() { var query = _em1.Customers.Select(c => c).OrderByDescending(c2 => c2.Address.Region); Assert.IsTrue(query.First().CustomerID == "SPLIR"); }
[Description("Select all customers ordered by the region, then the contact name.")] public void LinqToEntities59() { var query = _em1.Customers.Select(c => c) .OrderBy(c => c.Address.Region).ThenBy(c => c.ContactName); Assert.IsTrue(query.First().CustomerID == "ROMEY"); Assert.IsTrue(query.First().Address.City + "/" + query.First().ContactName == "Madrid/Alejandra Camino"); }
[Description("Select all customers ordered by the region in descending order, then the contact name.")] public void LinqToEntities60() { var query = _em1.Customers.Select(c => c) .OrderByDescending(c => c.Address.Region).ThenBy(c => c.ContactName); Assert.IsTrue(query.First().CustomerID == "SPLIR"); }
[Description("Select all customers ordered by the region then the contact name in descending order.")] public void LinqToEntities61() { var query = _em1.Customers.Select(c => c).OrderBy(c => c.Address.Region).ThenByDescending(c => c.ContactName); Assert.IsTrue(query.First().CustomerID == "WOLZA");
[Description("Select all products ordered by the descending unit price.")] public void LinqToEntities62() { var query = from p in _em1.Products orderby p.UnitPrice descending select p; Assert.IsTrue(query.First().ProductID == 38);
Assert.IsTrue(query.First().OrderDate == new DateTime(1997, 8, 25)); }
[Description("Select all Regions with a customer.")] public void LinqToEntities64() { var query = from c in _em1.Customers group c by c.Address.Region into regions select new { regions.Key }; Assert.IsTrue(query.Count() == 19);
var query2 = query.OrderByDescending(r => r.Key); Assert.IsTrue(query2.First().Key == "WY"); }
[Description("Select all dates with orders placed.")] public void LinqToEntities65() { var query = from o in _em1.Orders group o by o.OrderDate into dates select new { dates.Key }; Assert.IsTrue(query.Count() == 480);
var query2 = query.OrderBy(d => d.Key); Assert.IsTrue(query2.First().Key == new DateTime(1996, 7, 4)); }
[Description("Select all Regions and customer count for each region.")] public void LinqToEntities66() { var query = from c in _em1.Customers group c by c.Address.Region into regions orderby regions.Key select new { region = regions.Key, count = regions.Count() }; var r = query.ToList();
Assert.IsTrue(r.First().count == 60); // the variable count }
[Description("Select all Regions and customer count for each region as a method query.")] public void LinqToEntities67() { var query = _em1.Customers.GroupBy(c => c.Address.Region) .OrderByDescending(r => r.Key) .Select(r => new { region = r.Key, count = r.Count() }); Assert.IsTrue(query.First().count == 1); }
[Description("Select all Customer Regions with the total Freight on all orders for Customers in that Region.")] public void LinqToEntities68() { var query = from c in _em1.Customers group c by c.Address.Region into regions orderby regions.Key join c2 in _em1.Customers on regions.Key equals c2.Address.Region select new { region = regions.Key, total = c2.Orders.Sum(o => o.Freight) }; var r = query.ToList(); Assert.IsTrue(query.First().region == null); Assert.IsTrue(query.First().total == 225.58M); }
[Description("Select all Customer Regions with the total Freight on all orders for Customers in that Region as a method query.")] public void LinqToEntities69() { var query = _em1.Customers.GroupBy(c => c.Address.Region) .OrderBy(r => r.Key) .Select(g => new { Region = g.Key, FreightTotal = g .SelectMany(c2 => c2.Orders) .Sum(o => o.Freight) });
var query2 = query.OrderByDescending(r => r.Region); Assert.IsTrue(query2.First().Region == "WY"); Assert.IsTrue(query2.First().FreightTotal == 558.67M); } VB <Description("Select all customers ordered by ContactName.")> Public Sub LinqToEntities53() Dim query = From c In _em1.Customers Order By c.ContactName Select c Assert.IsTrue(query.First().ContactName = "Alejandra Camino") End Sub
<Description("Select all customers ordered by ContactName descending.")> Public Sub LinqToEntities54() Dim query = From c In _em1.Customers Order By c.CompanyName Descending Select c Assert.IsTrue(query.First().CompanyName = "Wolski Zajazd") End Sub
<Description("Select an anonoymous type with all product IDs ordered by UnitsInStock.")> Public Sub LinqToEntities55() Dim query = From p In _em1.Products Order By p.UnitsInStock Select New With {Key p.ProductID, Key p.UnitsInStock} Assert.IsTrue(query.First().ProductID = 5) End Sub
<Description("Select an anonoymous type with all product IDs ordered by UnitsInStock as a method query.")> Public Sub LinqToEntities56() Dim query = _em1.Products.OrderBy(Function(p) p.UnitsInStock).Select(Function(p2) New With {Key p2.ProductID, Key p2.UnitsInStock}) Assert.IsTrue(query.First().ProductID = 5) Assert.IsTrue(query.First().UnitsInStock = 0) End Sub
<Description("Select all customers ordered by the descending region.")> Public Sub LinqToEntities57() Dim query = From c In _em1.Customers Order By c.Address.Region Descending Select c Assert.IsTrue(query.First().CustomerID = "SPLIR") Assert.IsTrue(query.First().Address.Region = "WY") End Sub
<Description("Select all customers ordered by the descending region as a method query.")> Public Sub LinqToEntities58() Dim query = _em1.Customers.Select(Function(c) c).OrderByDescending(Function(c2) c2.Address.Region) Assert.IsTrue(query.First().CustomerID = "SPLIR") End Sub
<Description("Select all customers ordered by the region, then the contact name.")> Public Sub LinqToEntities59() Dim query = _em1.Customers.Select(Function(c) c).OrderBy(Function(c) c.Address.Region).ThenBy(Function(c) c.ContactName) Assert.IsTrue(query.First().CustomerID = "ROMEY") Assert.IsTrue(query.First().Address.City & "/" & query.First().ContactName = "Madrid/Alejandra Camino") End Sub
<Description("Select all customers ordered by the region in descending order, then the contact name.")> Public Sub LinqToEntities60() Dim query = _em1.Customers.Select(Function(c) c).OrderByDescending(Function(c) c.Address.Region).ThenBy(Function(c) c.ContactName) Assert.IsTrue(query.First().CustomerID = "SPLIR") End Sub
<Description("Select all customers ordered by the region then the contact name in descending order.")> Public Sub LinqToEntities61() Dim query = _em1.Customers.Select(Function(c) c).OrderBy(Function(c) c.Address.Region).ThenByDescending(Function(c) c.ContactName) Assert.IsTrue(query.First().CustomerID = "WOLZA")
'//Alternate 'var query = _Em1.Customers.OrderBy(c => c.Address.Region).ThenByDescending(c => c.ContactName).Select(c => c); End Sub
<Description("Select all products ordered by the descending unit price.")> Public Sub LinqToEntities62() Dim query = From p In _em1.Products Order By p.UnitPrice Descending Select p Assert.IsTrue(query.First().ProductID = 38)
'// Alternate 'var query0 = _Em1.Products.OrderByDescending(p => p.UnitPrice).Select(p => p); 'Assert.IsTrue(query0.First().ProductID == 38);} End Sub
<Description("Select all orders for a customer ordered by date that the order was placed.")> Public Sub LinqToEntities63()
Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").SelectMany(Function(c) c.Orders.Select(Function(o) o)).OrderBy(Function(o2) o2.OrderDate)
Assert.IsTrue(query.First().OrderDate = New Date(1997, 8, 25)) End Sub
<Description("Select all Regions with a customer.")> Public Sub LinqToEntities64() Dim query = From c In _em1.Customers Group c By c.Address.Region Into regions = Group Select New With {Key Region} Assert.IsTrue(query.Count() = 19)
Dim query2 = query.OrderByDescending(Function(r) r.Key) Assert.IsTrue(query2.First().Key = "WY") End Sub
<Description("Select all dates with orders placed.")> Public Sub LinqToEntities65() Dim query = From o In _em1.Orders Group o By o.OrderDate Into dates = Group Select New With {Key OrderDate} Assert.IsTrue(query.Count() = 480)
Dim query2 = query.OrderBy(Function(d) d.Key) Assert.IsTrue(query2.First().Key = New Date(1996, 7, 4)) End Sub
<Description("Select all Regions and customer count for each region.")> Public Sub LinqToEntities66() Dim query = From c In _em1.Customers Group c By c.Address.Region Into regions = Group Order By Region Select New With {Key .region = Region, Key .count = regions.Count()} Dim r = query.ToList()
Assert.IsTrue(r.First().count = 60) ' the variable count End Sub
<Description("Select all Regions and customer count for each region as a method query.")> Public Sub LinqToEntities67() Dim query = _em1.Customers.GroupBy(Function(c) c.Address.Region).OrderByDescending(Function(r) r.Key).Select(Function(r) New With {Key .region = r.Key, Key .count = r.Count()}) Assert.IsTrue(query.First().count = 1) End Sub
<Description("Select all Customer Regions with the total Freight on all orders for Customers in that Region.")> Public Sub LinqToEntities68() Dim query = From c In _em1.Customers Group c By c.Address.Region Into regions = Group Order By Region Join c2 In _em1.Customers On Region Equals c2.Address.Region Select New With {Key .region = Region, Key .total = c2.Orders.Sum(Function(o) o.Freight)} Dim r = query.ToList() Assert.IsTrue(query.First().region Is Nothing) Assert.IsTrue(query.First().total = 225.58D) End Sub
<Description("Select all Customer Regions with the total Freight on all orders for Customers in that Region as a method query.")> Public Sub LinqToEntities69() Dim query = _em1.Customers.GroupBy(Function(c) c.Address.Region).OrderBy(Function(r) r.Key).Select(Function(g) New With {Key .Region = g.Key, Key .FreightTotal = g.SelectMany(Function(c2) c2.Orders).Sum(Function(o) o.Freight)})
Assert.IsTrue(query.First().Region Is Nothing) Assert.IsTrue(query.First().FreightTotal = 38063.31D)
Dim query2 = query.OrderByDescending(Function(r) r.Region) Assert.IsTrue(query2.First().Region = "WY") Assert.IsTrue(query2.First().FreightTotal = 558.67D) End Sub
Aggregate operators - Count, Sum, Min, Max, Average Examples of the LINQ Count, Sum, Min, Max and Average operators are shown below. In the examples below _em1 is an EntityManager. C# [Description("This sample uses COUNT to get the number of Orders.")] public void LinqToEntities23() { var query = _em1.Orders.Count(); Assert.IsTrue(query == 830); }
[Description("This sample uses COUNT to get the number of Orders placed by Customers in Mexico.")] public void LinqToEntities24() { var query = _em1.Orders.Where(o => o.Customer.Address.Country == "Mexico").Count(); Assert.IsTrue(query == 28); }
[Description("This sample uses COUNT to get the number of Orders shipped to Mexico.")] public void LinqToEntities25() { var query = _em1.Orders .Where(o => o.ShipCountry == "Mexico").Count(); Assert.IsTrue(query == 28); }
[Description("This sample uses SUM to find the total freight over all Orders.")] public void LinqToEntities26() { var query = _em1.Orders.Select(o => o.Freight).Sum(); Assert.IsTrue(query == 64942.69M); }
[Description("This sample uses SUM to find the total number of units on order over all Products.")] public void LinqToEntities27() { var query = _em1.Products.Sum(p => p.UnitsOnOrder); Assert.IsTrue(query == 780); }
[Description("This sample uses SUM to find the total number of units on order over all Products out-of-stock.")] public void LinqToEntities28() { var query = _em1.Products.Where(p => p.UnitsInStock == 0).Sum(p => p.UnitsOnOrder); Assert.IsTrue(query == 70); }
[Description("This sample uses MIN to find the lowest unit price of any Product.")] public void LinqToEntities29() { var query = _em1.Products.Select(p => p.UnitPrice).Min(); Assert.IsTrue(query == 2.5M); }
[Description("This sample uses MIN to find the lowest freight of any Order.")] public void LinqToEntities30() { var query = _em1.Orders.Min(o => o.Freight); Assert.IsTrue(query == 0.02M); }
[Description("This sample uses MIN to find the lowest freight of any Order shipped to Mexico.")] public void LinqToEntities31() { var query = _em1.Orders.Where(o => o.ShipCountry == "Mexico").Min(o => o.Freight); Assert.IsTrue(query == 0.4M); var query2 = _em1.Orders.Where(o => o.ShipCountry == "Mexico").Select(o => o.Freight).Min(); Assert.IsTrue(query2 == 0.4M); }
[Description("This sample uses Min to find the Products that have the lowest unit price " + "in each category, and returns the result as an anonoymous type.")] public void LinqToEntities32() { var query = from p in _em1.Products group p by p.Category.CategoryID into g orderby g.Key select new { CategoryID = g.Key, CheapestProducts = from p2 in g where p2.UnitPrice == g.Min(p3 => p3.UnitPrice) select p2 };
var r = query.ToList(); Assert.IsTrue(r.Count() == 8); Assert.IsTrue(r.First().CategoryID == 1); Assert.IsTrue(r.First().CheapestProducts.First().UnitPrice == 4.5M); }
[Description("This sample uses MAX to find the latest hire date of any Employee.")] public void LinqToEntities33() { var query = _em1.Employees.Select(e => e.HireDate).Max(); Assert.IsTrue(query == new DateTime(1994, 11, 15)); }
[Description("This sample uses MAX to find the most units in stock of any Product.")] public void LinqToEntities34() { var query = _em1.Products.Max(p => p.UnitsInStock); Assert.IsTrue(query == 125); }
[Description("This sample uses MAX to find the most units in stock of any Product with CategoryID = 1.")] public void LinqToEntities35() { var query = _em1.Products.Where(p => p.Category.CategoryID == 2).Max(p => p.UnitsInStock); Assert.IsTrue(query == 120); }
[Description("This sample uses MAX to find the Products that have the " + "highest unit price in each category, and returns the result as an anonoymous type.")] public void LinqToEntities36() { var query = from p in _em1.Products group p by p.Category.CategoryID into g orderby g.Key select new { g.Key, MostExpensiveProducts = from p2 in g where p2.UnitPrice == g.Max(p3 => p3.UnitPrice) orderby p2.UnitPrice select p2 };
var r = query.ToList(); Assert.IsTrue(r.Count() == 8); Assert.IsTrue(r.First().Key == 1); Assert.IsTrue(r.First().MostExpensiveProducts.First().UnitPrice == 263.5M); }
[Description("This sample uses AVERAGE to find the average freight of all Orders.")] public void LinqToEntities37() { var query = _em1.Orders.Select(o => o.Freight).Average(); Assert.IsTrue(query == 78.2442M); }
[Description("This sample uses AVERAGE to find the average unit price of all Products.")] public void LinqToEntities38() { var query = _em1.Products.Average(p => p.UnitPrice); Assert.IsTrue(query == 28.8663M); }
[Description("This sample uses AVERAGE to find the average unit price of all Products with CategoryID = 1.")] public void LinqToEntities39() { var query = _em1.Products.Where(p => p.Category.CategoryID == 1) .Average(p => p.UnitPrice); Assert.IsTrue(query == 37.9791M); }
[Description("This sample uses AVERAGE to find the Products that have unit price higher than the average unit price of the category for each category.")] public void LinqToEntities40() {
var query = from p in _em1.Products group p by p.Category.CategoryID into g orderby g.Key descending select new { g.Key, ExpensiveProducts = from p2 in g where p2.UnitPrice > g.Average(p3 => p3.UnitPrice) orderby p2.UnitPrice select p2 };
var r = query.ToList(); Assert.IsTrue(r.Count() == 8); Assert.IsTrue(r.First().Key == 8); }
[Description("This sample uses AVERAGE to find the average unit price of each category.")] public void LinqToEntities41() { var query = from p in _em1.Products group p by p.Category.CategoryID into g orderby g.Key descending select new { g.Key, Average = g.Average(p => p.UnitPrice) }; Assert.IsTrue(query.ToList().First().Key == 8); Assert.IsTrue(query.First().Average == 20.6825M); } VB <Description("This sample uses COUNT to get the number of Orders.")> Public Sub LinqToEntities23() Dim query = _em1.Orders.Count() Assert.IsTrue(query = 830) End Sub
<Description("This sample uses COUNT to get the number of Orders placed by Customers in Mexico.")> Public Sub LinqToEntities24() Dim query = _em1.Orders.Where(Function(o) o.Customer.Address.Country = "Mexico").Count() Assert.IsTrue(query = 28) End Sub
<Description("This sample uses COUNT to get the number of Orders shipped to Mexico.")> Public Sub LinqToEntities25() Dim query = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Count() Assert.IsTrue(query = 28) End Sub
<Description("This sample uses SUM to find the total freight over all Orders.")> Public Sub LinqToEntities26() Dim query = _em1.Orders.Select(Function(o) o.Freight).Sum() Assert.IsTrue(query Is 64942.69D) End Sub
<Description("This sample uses SUM to find the total number of units on order over all Products.")> Public Sub LinqToEntities27() Dim query = _em1.Products.Sum(Function(p) p.UnitsOnOrder) Assert.IsTrue(query = 780) End Sub
<Description("This sample uses SUM to find the total number of units on order over all Products out-of-stock.")> Public Sub LinqToEntities28() Dim query = _em1.Products.Where(Function(p) p.UnitsInStock = 0).Sum(Function(p) p.UnitsOnOrder) Assert.IsTrue(query = 70) End Sub
<Description("This sample uses MIN to find the lowest unit price of any Product.")> Public Sub LinqToEntities29() Dim query = _em1.Products.Select(Function(p) p.UnitPrice).Min() Assert.IsTrue(query Is 2.5D) End Sub
<Description("This sample uses MIN to find the lowest freight of any Order.")> Public Sub LinqToEntities30() Dim query = _em1.Orders.Min(Function(o) o.Freight) Assert.IsTrue(query Is 0.02D) End Sub
<Description("This sample uses MIN to find the lowest freight of any Order shipped to Mexico.")> Public Sub LinqToEntities31() Dim query = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Min(Function(o) o.Freight) Assert.IsTrue(query Is 0.4D) Dim query2 = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o.Freight).Min() Assert.IsTrue(query2 Is 0.4D) End Sub
<Description("This sample uses Min to find the Products that have the lowest unit price " & "in each category, and returns the result as an anonoymous type.")> Public Sub LinqToEntities32() Dim query = From p In _em1.Products Group p By p.Category.CategoryID Into g = Group Order By CategoryID Select New With {Key .CategoryID = CategoryID, Key .CheapestProducts = From p2 In g Where p2.UnitPrice = g.Min(Function(p3) p3.UnitPrice) Select p2}
Dim r = query.ToList() Assert.IsTrue(r.Count() = 8) Assert.IsTrue(r.First().CategoryID = 1) Assert.IsTrue(r.First().CheapestProducts.First().UnitPrice = 4.5D) End Sub
<Description("This sample uses MAX to find the latest hire date of any Employee.")> Public Sub LinqToEntities33() Dim query = _em1.Employees.Select(Function(e) e.HireDate).Max() Assert.IsTrue(query Is New Date(1994, 11, 15)) End Sub
<Description("This sample uses MAX to find the most units in stock of any Product.")> Public Sub LinqToEntities34() Dim query = _em1.Products.Max(Function(p) p.UnitsInStock) Assert.IsTrue(query = 125) End Sub
<Description("This sample uses MAX to find the most units in stock of any Product with CategoryID = 1.")> Public Sub LinqToEntities35() Dim query = _em1.Products.Where(Function(p) p.Category.CategoryID = 2).Max(Function(p) p.UnitsInStock) Assert.IsTrue(query = 120) End Sub
<Description("This sample uses MAX to find the Products that have the " & "highest unit price in each category, and returns the result as an anonoymous type.")> Public Sub LinqToEntities36() Dim query = From p In _em1.Products Group p By p.Category.CategoryID Into g = Group Order By CategoryID Select New With {Key CategoryID, Key .MostExpensiveProducts = From p2 In g Where p2.UnitPrice = g.Max(Function(p3) p3.UnitPrice) Order By p2.UnitPrice Select p2}
Dim r = query.ToList() Assert.IsTrue(r.Count() = 8) Assert.IsTrue(r.First().Key = 1) Assert.IsTrue(r.First().MostExpensiveProducts.First().UnitPrice = 263.5D) End Sub
<Description("This sample uses AVERAGE to find the average freight of all Orders.")> Public Sub LinqToEntities37() Dim query = _em1.Orders.Select(Function(o) o.Freight).Average() Assert.IsTrue(query Is 78.2442D) End Sub
<Description("This sample uses AVERAGE to find the average unit price of all Products.")> Public Sub LinqToEntities38() Dim query = _em1.Products.Average(Function(p) p.UnitPrice) Assert.IsTrue(query Is 28.8663D) End Sub
<Description("This sample uses AVERAGE to find the average unit price of all Products with CategoryID = 1.")> Public Sub LinqToEntities39() Dim query = _em1.Products.Where(Function(p) p.Category.CategoryID = 1).Average(Function(p) p.UnitPrice) Assert.IsTrue(query Is 37.9791D) End Sub
<Description("This sample uses AVERAGE to find the Products that have unit price higher than the average unit price of the category for each category.")> Public Sub LinqToEntities40()
Dim query = From p In _em1.Products Group p By p.Category.CategoryID Into g = Group Order By CategoryID Descending Select New With {Key CategoryID, Key .ExpensiveProducts = From p2 In g Where p2.UnitPrice > g.Average(Function(p3) p3.UnitPrice) Order By p2.UnitPrice Select p2}
Dim r = query.ToList() Assert.IsTrue(r.Count() = 8) Assert.IsTrue(r.First().Key = 8) End Sub
<Description("This sample uses AVERAGE to find the average unit price of each category.")> Public Sub LinqToEntities41() Dim query = From p In _em1.Products Group p By p.Category.CategoryID Into g = Group Order By CategoryID Descending Select New With {Key CategoryID, Key .Average = g.Average(Function(p) p.UnitPrice)} Assert.IsTrue(query.ToList().First().Key = 8) Assert.IsTrue(query.First().Average = 20.6825D) End Sub
Set operators - First, Distinct, Union, Concat, Interset, Except Examples of the LINQ First, Distinct, Union, Concat, Intercept and Except operators are shown below. In the examples below _em1 is an EntityManager. C# [Description("This sample uses FIRST and WHERE to get the first (database order) order that is shipped to Seattle. The WHERE predicate is evaluated on the server.")] public void LinqToEntities42() { var query = from o in _em1.Orders where o.ShipCity == "Seattle" orderby o.OrderID select o;
var result = query.First(); Assert.IsTrue(result.OrderID == 10269); }
[Description("This sample uses FIRST to get the first (database order) order that is shipped to Seattle. The predicate is evaluated on the client.")] public void LinqToEntities43() { var query = from o in _em1.Orders orderby o.OrderID select o; var result = query .First(x => x.ShipCity == "Bern"); Assert.IsTrue(result.OrderID == 10254); }
[Description("This sample uses FIRST, WHERE and ORDER BY to get the first order that is shipped to Seattle, ordered by date. The predicate is evaluated on the server.")] public void LinqToEntities44() { var query = from o in _em1.Orders where o.ShipCity == "Seattle" orderby o.OrderDate select o;
var result = query.First(); Assert.IsTrue(result.OrderID == 10269); }
[Description("This sample uses DISTINCT to get all the categories of products.")] public void LinqToEntities45() { var query = _em1.Products.Select(o => o.Category).Distinct().OrderByDescending(c => c.CategoryName); Assert.IsTrue(query.Count() == 8); Assert.IsTrue(query.First().CategoryName == "Seafood"); }
[Description("This sample uses UNION to get all the orders where the shipping country was Mexico or Canada.")] public void LinqToEntities46() { var mexico = _em1.Orders.Where(o => o.ShipCountry == "Mexico").Select(o => o); var canada = _em1.Orders.Where(o => o.ShipCountry == "Canada").Select(o => o); var query = mexico.Union(canada).OrderBy(o => o.OrderID);
[Description("This sample uses UNION and DISTINCT to get all the Customers from orders where the shipping country was Mexico or Canada.")] public void LinqToEntities47() { var mexico = _em1.Orders.Where(o => o.ShipCountry == "Mexico").Select(o => o); var canada = _em1.Orders.Where(o => o.ShipCountry == "Canada").Select(o => o); var union = mexico.Union(canada).Select(o => o.Customer);
var query = union.Distinct().OrderByDescending(c => c.CompanyName);
[Description("This sample uses CONCAT to get all orders where the shipping country was Mexico or Canada.")] public void LinqToEntities48() { var mexico = _em1.Orders.Where(o => o.ShipCountry == "Mexico").OrderBy(o => o.OrderID).Select(o => o); var canada = _em1.Orders.Where(o => o.ShipCountry == "Canada").OrderBy(o => o.OrderID).Select(o => o);
var query = mexico.Concat(canada);
Assert.IsTrue(query.Count() == 58); var r = query.ToList(); Assert.IsTrue(r.First().OrderID == 10259); }
[Description("This sample uses INTERSECT to get common products where an order was shipped to Mexico or Canada.")] public void LinqToEntities49() { var mexico = _em1.OrderDetails.Where(od => od.Order.ShipCountry == "Mexico").OrderBy(o => o.ProductID).Select(od => od.Product); var canada = _em1.OrderDetails.Where(od => od.Order.ShipCountry == "Canada").OrderBy(o => o.ProductID).Select(od => od.Product);
[Description("This sample uses INTERSECT to get common products where an order was shipped to Mexico " + "or USA in one consolidated query.")] public void LinqToEntities50() { var query = _em1.OrderDetails.Where(od => od.Order.ShipCountry == "Mexico") .Select(od => od.Product).Intersect(_em1.OrderDetails .Where(od => od.Order.ShipCountry == "USA").Select(o => o.Product)); Assert.IsTrue(query.Count() == 44); }
[Description("This sample uses EXCEPT to get customers who shipped orders to Mexico but not Canada.")] public void LinqToEntities51() { var query = _em1.Orders.Where(o => o.ShipCountry == "Mexico") .Select(o => o.Customer).Except(_em1.Orders .Where(o => o.ShipCountry == "Canada").Select(o => o.Customer)); Assert.IsTrue(query.Count() == 5); }
[Description("Variation on LinqToEntities51.")] public void LinqToEntities51b() { var mexico = _em1.Orders.Where(o => o.ShipCountry == "Mexico") .Select(o => o.Customer); var canada = _em1.Orders.Where(o => o.ShipCountry == "Canada") .Select(o => o.Customer); // diff => customers with orders shipping to Mexico but none shipping to Canada var diff = mexico.Except(canada);
[Description("This sample uses EXCEPT to get customers with no orders sent to Mexico.")] public void LinqToEntities52() { var query = _em1.Customers.Select(e => e) .Except(_em1.Orders.Where(o => o.ShipCountry == "Mexico") .Select(o => o.Customer)); Assert.IsTrue(query.Count() == 86); } VB <Description("This sample uses FIRST and WHERE to get the first (database order) order that is shipped to Seattle. The WHERE predicate is evaluated on the server.")> Public Sub LinqToEntities42() Dim query = From o In _em1.Orders Where o.ShipCity = "Seattle" Order By o.OrderID Select o
Dim result = query.First() Assert.IsTrue(result.OrderID = 10269) End Sub
<Description("This sample uses FIRST to get the first (database order) order that is shipped to Seattle. The predicate is evaluated on the client.")> Public Sub LinqToEntities43() Dim query = From o In _em1.Orders Order By o.OrderID Select o Dim result = query.First(Function(x) x.ShipCity = "Bern") Assert.IsTrue(result.OrderID = 10254) End Sub
<Description("This sample uses FIRST, WHERE and ORDER BY to get the first order that is shipped to Seattle, ordered by date. The predicate is evaluated on the server.")> Public Sub LinqToEntities44() Dim query = From o In _em1.Orders Where o.ShipCity = "Seattle" Order By o.OrderDate Select o
Dim result = query.First() Assert.IsTrue(result.OrderID = 10269) End Sub
<Description("This sample uses DISTINCT to get all the categories of products.")> Public Sub LinqToEntities45() Dim query = _em1.Products.Select(Function(o) o.Category).Distinct().OrderByDescending(Function(c) c.CategoryName) Assert.IsTrue(query.Count() = 8) Assert.IsTrue(query.First().CategoryName = "Seafood") End Sub
<Description("This sample uses UNION to get all the orders where the shipping country was Mexico or Canada.")> Public Sub LinqToEntities46() Dim mexico = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o) Dim canada = _em1.Orders.Where(Function(o) o.ShipCountry = "Canada").Select(Function(o) o) Dim query = mexico.Union(canada).OrderBy(Function(o) o.OrderID)
Assert.IsTrue(query.Count() = 58) Assert.IsTrue(query.First().OrderID = 10259) End Sub
<Description("This sample uses UNION and DISTINCT to get all the Customers from orders where the shipping country was Mexico or Canada.")> Public Sub LinqToEntities47() Dim mexico = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o) Dim canada = _em1.Orders.Where(Function(o) o.ShipCountry = "Canada").Select(Function(o) o) Dim union = mexico.Union(canada).Select(Function(o) o.Customer)
Dim query = union.Distinct().OrderByDescending(Function(c) c.CompanyName)
Assert.IsTrue(query.Count() = 8) Assert.IsTrue(query.First().CompanyName = "Tortuga Restaurante") End Sub
<Description("This sample uses CONCAT to get all orders where the shipping country was Mexico or Canada.")> Public Sub LinqToEntities48() Dim mexico = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").OrderBy(Function(o) o.OrderID).Select(Function(o) o) Dim canada = _em1.Orders.Where(Function(o) o.ShipCountry = "Canada").OrderBy(Function(o) o.OrderID).Select(Function(o) o)
Dim query = mexico.Concat(canada)
Assert.IsTrue(query.Count() = 58) Dim r = query.ToList() Assert.IsTrue(r.First().OrderID = 10259) End Sub
<Description("This sample uses INTERSECT to get common products where an order was shipped to Mexico or Canada.")> Public Sub LinqToEntities49() Dim mexico = _em1.OrderDetails.Where(Function(od) od.Order.ShipCountry = "Mexico").OrderBy(Function(o) o.ProductID).Select(Function(od) od.Product) Dim canada = _em1.OrderDetails.Where(Function(od) od.Order.ShipCountry = "Canada").OrderBy(Function(o) o.ProductID).Select(Function(od) od.Product)
Dim query = mexico.Intersect(canada)
Assert.IsTrue(query.Count() = 24) Dim productIds = query.Select(Function(p) p.ProductID).ToList() Assert.IsTrue(productIds.Contains(21)) End Sub
<Description("This sample uses INTERSECT to get common products where an order was shipped to Mexico " & "or USA in one consolidated query.")> Public Sub LinqToEntities50() Dim query = _em1.OrderDetails.Where(Function(od) od.Order.ShipCountry = "Mexico").Select(Function(od) od.Product).Intersect(_em1.OrderDetails.Where(Function(od) od.Order.ShipCountry = "USA").Select(Function(o) o.Product)) Assert.IsTrue(query.Count() = 44) End Sub
<Description("This sample uses EXCEPT to get customers who shipped orders to Mexico but not Canada.")> Public Sub LinqToEntities51() Dim query = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o.Customer).Except(_em1.Orders.Where(Function(o) o.ShipCountry = "Canada").Select(Function(o) o.Customer)) Assert.IsTrue(query.Count() = 5) End Sub
<Description("Variation on LinqToEntities51.")> Public Sub LinqToEntities51b() Dim mexico = _em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o.Customer) Dim canada = _em1.Orders.Where(Function(o) o.ShipCountry = "Canada").Select(Function(o) o.Customer) ' diff => customers with orders shipping to Mexico but none shipping to Canada Dim diff = mexico.Except(canada)
Assert.IsTrue(mexico.Count() = 28) Assert.IsTrue(canada.Count() = 30) Assert.IsTrue(diff.Count() = 5) End Sub
<Description("This sample uses EXCEPT to get customers with no orders sent to Mexico.")> Public Sub LinqToEntities52() Dim query = _em1.Customers.Select(Function(e) e).Except(_em1.Orders.Where(Function(o) o.ShipCountry = "Mexico").Select(Function(o) o.Customer)) Assert.IsTrue(query.Count() = 86) End Sub
Paging operators - Take, Skip Examples of the LINQ Take and Skip operators are shown below. In the examples below _em1 is an EntityManager. C# [Description("This sample uses WHERE to find all customers whose contact title is Sales Representative.")] public void LinqToEntities96a() {
// Not a variation of LinqToEntities96, but no numbers were available here.
[Description("Skip the most recent 2 orders from customers in London")] public void LinqToEntities96() { var query = _em1.Orders .Where(o => o.Customer.Address.City == "London") .OrderBy(o => o.OrderDate) .Skip(2).Select(o => o);
Assert.IsTrue(query.First().OrderID == 10359); }
[Description("Take the 2 most recent Orders ")] public void LinqToEntities97() { var query = _em1.Orders .OrderBy(o => o.OrderDate) .Take(2).Select(o => o);
[Description("Take the 10th to the 20th Orders, ordered by date ")] public void LinqToEntities98() { var query = _em1.Orders .OrderBy(o => o.OrderDate) .Skip(10).Take(10).Select(o => o);
query.QueryStrategy = QueryStrategy.DataSourceOnly; // because of skip operator
var r = query.ToList(); Assert.IsTrue(r.Count() == 10); Assert.IsTrue(r.First().OrderID == 10258); }
[Description("Use a page number variable to get the xth page")] public void LinqToEntities99() { int pageSize = 10; int pageNumber = 4;
var query = _em1.Orders .OrderBy(o => o.OrderDate) .Skip(pageSize * pageNumber).Take(pageSize).Select(o => o); query.QueryStrategy = QueryStrategy.DataSourceOnly; var r = query.ToList(); Assert.IsTrue(r.Count() == pageSize); Assert.IsTrue(r.First().OrderID == 10288); } VB <Description("This sample uses WHERE to find all customers whose contact title is Sales Representative.")> Public Sub LinqToEntities96a()
' Not a variation of LinqToEntities96, but no numbers were available here.
Dim customersQuery = _em1.Customers.OrderBy(Function(c) c.CompanyName) customersQuery.QueryStrategy = QueryStrategy.DataSourceOnly Dim customers As ICollection(Of Customer) = customersQuery.Skip(5).Take(5).ToList() Assert.IsTrue(customers.Count() = 5) customersQuery.QueryStrategy = QueryStrategy.CacheOnly Assert.IsTrue(customersQuery.Count() = 5) End Sub
<Description("Skip the most recent 2 orders from customers in London")> Public Sub LinqToEntities96() Dim query = _em1.Orders.Where(Function(o) o.Customer.Address.City = _ "London").OrderBy(Function(o) o.OrderDate).Skip(2).Select(Function(o) o)
Assert.IsTrue(query.First().OrderID = 10359) End Sub
<Description("Take the 2 most recent Orders ")> Public Sub LinqToEntities97() Dim query = _em1.Orders.OrderBy(Function(o) o.OrderDate).Take(2).Select(Function(o) o)
Assert.IsTrue(query.Count() = 2) Assert.IsTrue(query.First().OrderID = 10248) End Sub
<Description("Take the 10th to the 20th Orders, ordered by date ")> Public Sub LinqToEntities98() Dim query = _em1.Orders.OrderBy(Function(o) o.OrderDate).Skip(10).Take(10).Select(Function(o) o)
query.QueryStrategy = QueryStrategy.DataSourceOnly ' because of skip operator
Dim r = query.ToList() Assert.IsTrue(r.Count() = 10) Assert.IsTrue(r.First().OrderID = 10258) End Sub
<Description("Use a page number variable to get the xth page")> Public Sub LinqToEntities99() Dim pageSize As Integer = 10 Dim pageNumber As Integer = 4
Dim query = _em1.Orders.OrderBy(Function(o) o.OrderDate).Skip(pageSize * _ pageNumber).Take(pageSize).Select(Function(o) o) query.QueryStrategy = QueryStrategy.DataSourceOnly Dim r = query.ToList() Assert.IsTrue(r.Count() = pageSize) Assert.IsTrue(r.First().OrderID = 10288) End Sub Span operators Include Examples of using DevForce Include operator are shown below. In the examples below _em1 is an EntityManager. C# [Description("Load OrderDetails with Orders ")] public void LinqToEntities94() { var query0 = _em1.Orders.Include("OrderDetails") .Where(c => c.Customer.Address.City == "London").Select(o => o);
var query1 = query0.OrderBy(o => o.OrderID); var r1 = query1.ToList();
var o1 = query1.First(); var count1 = o1.OrderDetails.Count(); Assert.IsTrue(count1 == 2); }
[Description("Load OrderDetails and Products with Orders ")] public void LinqToEntities95() { var query = _em1.Orders .Include("OrderDetails") .Include("OrderDetails.Product") .Take(3).Select(o => o);
var query2 = query.OrderByDescending(o => o.OrderID); var r2 = query2.ToList(); var p = r2.First().OrderDetails.First().Product; Assert.IsNotNull(p); Assert.IsFalse(p.EntityAspect.IsNullOrPendingEntity); } VB <Description("Load OrderDetails with Orders ")> Public Sub LinqToEntities94() Dim query0 = _em1.Orders.Include("OrderDetails").Where(Function(c) _ c.Customer.Address.City = "London").Select(Function(o) o)
Dim query1 = query0.OrderBy(Function(o) o.OrderID) Dim r1 = query1.ToList()
Dim o1 = query1.First() Dim count1 = o1.OrderDetails.Count() Assert.IsTrue(count1 = 2) End Sub
<Description("Load OrderDetails and Products with Orders ")> Public Sub LinqToEntities95() Dim query = _em1.Orders.Include("OrderDetails").Include _ ("OrderDetails.Product").Take(3).Select(Function(o) o)
Dim query2 = query.OrderByDescending(Function(o) o.OrderID) Dim r2 = query2.ToList() Dim p = r2.First().OrderDetails.First().Product Assert.IsNotNull(p) Assert.IsFalse(p.EntityAspect.IsNullOrPendingEntity) End Sub Relationship navigation - Using navigation properties in a query Examples of the LINQ queries involving property navigation are shown below. In the examples below _em1 is an EntityManager. C# [Description("Select a sequence of all the orders for a customer using Select.")] public void LinqToEntities70() { var query = _em1.Customers .Where(cust => cust.CustomerID == "ALFKI") .Select(c => c.Orders.OrderBy(o => o.OrderDate).Select(o => o));
Assert.IsTrue(query.Count() == 1); //query.First() is not available because this doesn't return a list }
[Description("Select all the orders for a customer using SelectMany.")] public void LinqToEntities71() { var query = _em1.Customers .Where(cust => cust.CustomerID == "ALFKI") .SelectMany(c => c.Orders); Assert.IsTrue(query.Count() == 6); Assert.IsTrue(query.First().OrderDate == new DateTime(1997, 8, 25)); }
[Description("Select number of orders placed in 1998 for a customer.")] public void LinqToEntities74() { var query = _em1.Customers .Where(cust => cust.CustomerID == "ALFKI") .SelectMany(c => c.Orders) .Where(o => o.OrderDate.HasValue == true && o.OrderDate.Value.Year == 1998); Assert.IsTrue(query.Count() == 3);
var query2 = query.OrderBy(o => o.OrderDate); Assert.IsTrue(query2.First().OrderDate == new DateTime(1998, 1, 15)); }
[Description("Select a customer and the sum of the freight of thier orders.")] public void LinqToEntities73() { var query = _em1.Customers .Where(cust => cust.CustomerID == "ALFKI") .Select(c => c.Orders.Sum(o => o.Freight)); Assert.IsTrue(query.First() == 225.58M); }
[Description("Select customers with an order where the shipping address is the same as the customers.")] public void LinqToEntities75() { var query = _em1.Customers .Where(cust => cust.Orders .Any(o => o.ShipAddress == cust.Address.Address)) .Select(c2 => c2); Assert.IsTrue(query.Count() == 83);
[Description("Selects all regions with a customer, and shows the sum of orders for customers for each region.")] public void LinqToEntities76() { var query = from c in _em1.Customers group c by c.Address.Region into regions join c2 in _em1.Customers on regions.Key equals c2.Address.Region orderby regions.Key descending select new { region = regions.Key, total = c2.Orders.Sum(o => o.Freight) };
// The orderby clause above only works if it *follows*, rather than precedes, the // join statement. Somebody explain that to me sometime. - GTD
Assert.IsTrue(query.First().region == "WY"); Assert.IsTrue(query.First().total == 558.67M); } VB <Description("Select a sequence of all the orders for a customer using Select.")> Public Sub LinqToEntities70() Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").Select(Function(c) c.Orders.OrderBy(Function(o) o.OrderDate).Select(Function(o) o))
Assert.IsTrue(query.Count() = 1) 'query.First() is not available because this doesn't return a list End Sub
<Description("Select all the orders for a customer using SelectMany.")> Public Sub LinqToEntities71() Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").SelectMany(Function(c) c.Orders) Assert.IsTrue(query.Count() = 6) Assert.IsTrue(query.First().OrderDate = New Date(1997, 8, 25)) End Sub
<Description("Select number of orders placed in 1998 for a customer.")> Public Sub LinqToEntities74() Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").SelectMany(Function(c) c.Orders).Where(Function(o) o.OrderDate.HasValue = True AndAlso o.OrderDate.Value.Year = 1998) Assert.IsTrue(query.Count() = 3)
Dim query2 = query.OrderBy(Function(o) o.OrderDate) Assert.IsTrue(query2.First().OrderDate = New Date(1998, 1, 15)) End Sub
<Description("Select a customer and the sum of the freight of thier orders.")> Public Sub LinqToEntities73() Dim query = _em1.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").Select(Function(c) c.Orders.Sum(Function(o) o.Freight)) Assert.IsTrue(query.First() = 225.58D) End Sub
<Description("Select customers with an order where the shipping address is the same as the customers.")> Public Sub LinqToEntities75() Dim query = _em1.Customers.Where(Function(cust) cust.Orders.Any(Function(o) o.ShipAddress = cust.Address.Address)).Select(Function(c2) c2) Assert.IsTrue(query.Count() = 83)
Dim query2 = query.OrderBy(Function(c) c.CompanyName) Assert.IsTrue(query.First().CompanyName = "Alfreds Futterkiste") End Sub
<Description("Selects all regions with a customer, and shows the sum of orders for customers for each region.")> Public Sub LinqToEntities76() Dim query = From c In _em1.Customers Group c By c.Address.Region Into regions = Group Join c2 In _em1.Customers On Region Equals c2.Address.Region Order By Region Descending Select New With {Key .region = Region, Key .total = c2.Orders.Sum(Function(o) o.Freight)}
' The orderby clause above only works if it *follows*, rather than precedes, the ' join statement. Somebody explain that to me sometime. - GTD
Assert.IsTrue(query.First().region = "WY") Assert.IsTrue(query.First().total = 558.67D) End Sub
Join operators - When to use Join This topic explains how to use the LINQ J oin() operator ... and why you should rarely use it.
LINQ has a J oin() operator. Developers who are new to Entity Framework (EF) are quick to use it because "JOIN" is such a common SQL operation. Experienced EF developers prefer to use subqueries and almost never need to use J oin(). Why? Primarily because the entity data model (EDM) represents relationships between entities as associations. You build that model so you can escape the mechanical details and think in object terms rather than database terms. You want to write anOrder.OrderDetails without getting into the nitty-gritty of using an outer-left-join of Order.OrderID and OrderDetail.OrderID. You'd prefer not to think about foreign keys at all; they're a concept that is alien to object thinking. The Join() operation breaks that abstraction. It necessarily forces you to think about precisely how you get the OrderDetails related to an order. You have to express the Join() in terms of Order.OrderID and OrderDetail.OrderID. Why do that if you don't have to? All abstractions "leak" eventually; it's unavoidable in real world programming. But we strive to retain the benefits of our abstractions while it is easy and prudent to do so. Life without joins Suppose we want to query for just those Customers that have placed an Order with our company. In SQL you'd write a "JOIN". In EF LINQ you'd write a subquery instead and test to see if there were any orders. That query written in comprehension syntax could look like this:
query = from c in manager.Customers where c.Orders.Any() select c
results = query.ToList() The manager variable in these examples is an instance of the NorthwindIBEntityManager generated from a model that accesses the "NorthwindIB" tutorial database. If you prefer method chaining (lambda) syntax you could write it this way: C# query = manager.Customers.Where(c => c.Orders.Any()) results = query.ToList(); VB query = manager.Customers.Where(Function (c) c.Orders.Any()) results = query.ToList() We're taking advantage of the fact that we've modeled the association between Customer and Order. We never need to JOIN when we have an association. You could have joined You could have achieved the same effect with a LINQ Join(). First with comprehension syntax.
query = from c in manager.Customers join o in manager.Orders on c.CustomerID equals o.CustomerID select c
results = query.Distinct().ToList() That's a lot of messy detail. We're fortunate that Customer has a single value key; it gets very messy if Customer has a composite key. Don't forget the Distinct() method or you'll get 800+ Customers, one for every Order in the tutorial database, when you only want the ~80 distinct Customers. Here it is again in method chaining (lambda) syntax: C# query = manager .Customers // Customers .Join(manager.Orders, // Orders c => c.CustomerID, // Customers key o => o.CustomerID, // Order key (c, o) => c); // projected value (the "select")
results = query.Distinct().ToList(); VB query = manager .Customers ' Customers .Join(manager.Orders, ' Orders Function(c) c.CustomerID, ' Customers key Function(o) o.CustomerID, ' Order key Function(c, o) c) ' projected value (the "select")
results = query.Distinct().ToList() When you have to join Occasionally you know that two entities are related even though there is no association in the model. Imagine that the Employee entity has a deprecated key (EmployeeNumber) which remains the only basis for linking to historical data (OldEmployeeData). Entity Framework won't let you associate a dependent entity's key (OldEmployeeData.EmployeeNumber) with a non-primary key of the parent (Employee.EmployeeNumber). You'll have to join them. We don't have data like that in the DevForce "NorthwindIB" tutorial database so we'll make up an absurd example in which you query for the Employees whose IDs happen also to be Product IDs. You can join these entities yourself; Employee and Product have integer IDs with values that overlap. The values that you join must be comparable with equality. A common mistake is to join on two properties that have different data types. You'll learn of your error in a runtime exception. Here's the query in comprehension syntax:
query = from e in manager.Employees join p in manager.Products on e.EmployeeID equals p.ProductID select e
results = query.ToList() The results include all nine Employees in the "NorthwindIB" tutorial database; they happen to have ids in the range {1..9} as do the first nine Products. Distinct() wasn't necessary because the query compares primary key values which are necessarily unique. Here's the same query in method chaining (lambda) syntax: C# query = manager .Employees .Join( manager.Products, e => e.EmployeeID, p => p.ProductID, (e, p) => e);
Using closures Examples of using closures with a LINQ query are shown below. In the examples below _em1 is an EntityManager. C# class MyClass { public static decimal Val = 50;
public decimal GetVal() { return MyClass.Val; } }
[Description("Uses a local variable as a query parameter.")] public void LinqToEntities91() { MyClass c = new MyClass();
[Description("Uses a the value of the local variable at query execution time.")] public void LinqToEntities92() { decimal x = 50; var query = _em1.Orders.Where(o => o.Freight > x).Select(o => new { o.Freight, o }); Assert.IsTrue(query.Count() == 360); x = 100; Assert.IsTrue(query.Count() == 187); } VB Friend Class MyClass Public Shared Val As Decimal = 50
Public Function GetVal() As Decimal Return MyClass.Val End Function End Class
<Description("Uses a local variable as a query parameter.")> Public Sub LinqToEntities91() Dim c As New MyClass()
Dim query = _em1.Orders.Where(Function(o) o.Freight > MyClass.Val).Select(Function(o) o) Assert.IsTrue(query.Count() = 360) End Sub
<Description("Uses a the value of the local variable at query execution time.")> Public Sub LinqToEntities92() Dim x As Decimal = 50 Dim query = _em1.Orders.Where(Function(o) o.Freight > x).Select(Function(o) New With {Key o.Freight, Key o}) Assert.IsTrue(query.Count() = 360) x = 100 Assert.IsTrue(query.Count() = 187) End Sub
Inheritance operators OfType Examples of the LINQ OfType operator are shown below. In the examples below _em1 is an EntityManager. C# [Description("Select all products, both active and discontinued products, and show the type.")] public void LinqToEntities77() { var query = _em1 .Products .Select(p => p);
var query2 = query // force local execution to show local type .AsEnumerable() .Select(p => new { type = p.GetType().ToString(), prod = p });
StringBuilder msg = new StringBuilder(); foreach (var aProduct in query2) { msg.Append("(" + aProduct.type + ") " + aProduct.prod.ProductName + Environment.NewLine); } // set break point and inspect msg.ToString() as desired
[Description("Select only discontinued products.")] public void LinqToEntities78() { var query = _em1.Products.OfType<DiscontinuedProduct>().Select(p => p); var r = query.ToList(); Assert.IsTrue(r.Count() == 8); }
[Description("Select only products, which will reutrn all Products and subtypes of Products (DiscontinuedProducts and ActiveProducts).")] public void LinqToEntities79() { var query = _em1.Products.OfType<Product>().Select(p => p); Assert.IsTrue(query.Count() == 77); } [Description("Select only discontinued products.")] public void LinqToEntities80() { // Similar to LinqToEntities78; no .Select clause var query = _em1.Products.OfType<DiscontinuedProduct>(); Assert.IsTrue(query.Count() == 8); }
[TestMethod] [Description("Select only discontinued products.")] public void LinqToEntities81() { var query = _em1.Products .Where(p => p is DiscontinuedProduct); Assert.IsTrue(query.Count() == 8); }
[Description("Select all current employees.")] public void LinqToEntities87() { var query = _em1.Employees.OfType<CurrentEmployee>() .ToList().Select(p => new { type = p.GetType().ToString(), p }); Assert.IsTrue(query.Count() >= 8); int lastId = query.OrderBy(e => e.p.EmployeeID).Last().p.EmployeeID; var query2 = query.OrderByDescending(e => e.p.EmployeeID); Assert.IsTrue(query2.First().p.EmployeeID == lastId); } VB <Description("Select all products, both active and discontinued products, and show the type.")> Public Sub LinqToEntities77() Dim query = _em1.Products.Select(Function(p) p)
' force local execution to show local type Dim query2 = query.AsEnumerable().Select(Function(p) New With {Key .type = p.GetType().ToString(), Key .prod = p})
Dim msg As New StringBuilder() For Each aProduct In query2 msg.Append("(" & aProduct.type & ") " & aProduct.prod.ProductName & Environment.NewLine) Next aProduct ' set break point and inspect msg.ToString() as desired
Dim query3 = query2.OrderBy(Function(p) p.prod.ProductName) Assert.IsTrue(query3.First().prod.ProductName = "Alice Mutton") End Sub
<Description("Select only discontinued products.")> Public Sub LinqToEntities78() Dim query = _em1.Products.OfType(Of DiscontinuedProduct)().Select(Function(p) p) Dim r = query.ToList() Assert.IsTrue(r.Count() = 8) End Sub
<Description("Select only products, which will reutrn all Products and subtypes of Products (DiscontinuedProducts and ActiveProducts).")> Public Sub LinqToEntities79() Dim query = _em1.Products.OfType(Of Product)().Select(Function(p) p) Assert.IsTrue(query.Count() = 77) End Sub <Description("Select only discontinued products.")> Public Sub LinqToEntities80() ' Similar to LinqToEntities78; no .Select clause Dim query = _em1.Products.OfType(Of DiscontinuedProduct)() Assert.IsTrue(query.Count() = 8) End Sub
<TestMethod, Description("Select only discontinued products.")> Public Sub LinqToEntities81() Dim query = _em1.Products.Where(Function(p) TypeOf p Is DiscontinuedProduct) Assert.IsTrue(query.Count() = 8) End Sub
<Description("Select all current employees.")> Public Sub LinqToEntities87() Dim query = _em1.Employees.OfType(Of CurrentEmployee)().ToList().Select(Function(p) New With {Key .type = p.GetType().ToString(), Key p}) Assert.IsTrue(query.Count() >= 8) Dim lastId As Integer = query.OrderBy(Function(e) e.p.EmployeeID).Last().p.EmployeeID Dim query2 = query.OrderByDescending(Function(e) e.p.EmployeeID) Assert.IsTrue(query2.First().p.EmployeeID = lastId) End Sub
More query tips In previous topics we've seen how to create a basic LINQ query, how to return part of an entity, how to include related entities, and more. Here are a few more miscellaneous query tips.
Create a Query with no EntityManager Attached You're familiar with the auto-generated query properties in your domain-specific EntityManager, that's what you're using whenever you do something like the following: C# var mgr = new NorthwindIBEntities(); var customerQuery = mgr.Customers; VB Dim mgr = New NorthwindIBEntities() Dim customerQuery = mgr.Customers These queries are "for" that EntityManager instance. If you use one of the query extension methods such as Execute or ExecuteAsync, the query will be executed by the EntityManager on which the query was created. C# var list = customerQuery.Execute(); VB Dim list = customerQuery.Execute() It's often useful to create a query that can be easily used with any EntityManager however. Suppose your application requires multiple EntityManagers because you need separate editing contexts - separate "sandboxes" - for contemporaneous editing sessions. You know what queries you will need to support the sandbox scenarios. Because you will re-use the query among several EntityManagers, you don't want to tie the query to any particular EntityManager. You can easily create a query without an EntityManager: C# EntityQuery<Customer> query = new EntityQuery<Customer>(); VB Dim query As New EntityQuery(Of Customer)() You then have a few choices for how you execute this query. One is to use the query methods on the EntityManager: C# var mgr = new NorthwindIBEntities(); var list = mgr.ExecuteQuery(query);
// ... and on another EM ...
var mgr2 = new NorthwindIBEntities(); var list2 = mgr2.ExecuteQuery(query); VB Dim mgr = New NorthwindIBEntities() Dim list = mgr.ExecuteQuery(query)
' ... and on another EM ...
Dim mgr2 = New NorthwindIBEntities() Dim list2 = mgr2.ExecuteQuery(query) You can also use the With extension method to target an EntityManager. You can use the With method for either an "unattached" query or one created for another EntityManager. C# var query = manager.Customers; var mgr2 = new NorthwindIBEntities(); var query2 = query.With(mgr2); VB Dim query = manager.Customers Dim mgr2 = New NorthwindIBEntities() Dim query2 = query.With(mgr2) If you execute a query without "attaching" it to an EntityManager in some way the DefaultManager will be used. The DefaultManager is a static property on the EntityManager which can reference the first EntityManager created, a specific EntityManager, or nothing. Use of the DefaultManager is deprecated, and we do not recommend it, as results may often not be what you expect. Existence queries: are there any entities that match? If you need to determine whether one or more entities meets certain criteria without retrieving the entities the Any LINQ operator is a good choice. C# string someName = "Some company name"; bool rc = manager.Customers.Any(c => c.CompanyName == someName); VB Dim someName As String = "Some company name" Dim rc As Boolean = manager.Customers.Any(Function(c) c.CompanyName = someName) As an "immediate execution" query, to use this asynchronously you must use AsScalarAsync: C# string someName = "Some company name"; var op = manager.Customers.AsScalarAsync().Any(c => c.CompanyName == someName); op.Completed += (o, args) => { bool rc = args.Result; }; VB Dim someName As String = "Some company name" Dim op = manager.Customers.AsScalarAsync().Any(Function(c) c.CompanyName = someName) AddHandler op.Completed, Sub(o, args) Dim rc As Boolean = args.Result The Count operator is also useful here, if instead of returning a boolean you want the total number matching the criteria. Use FirstOrNullEntity First, some explanation of First. The LINQ First operator, in all incarnations, will throw an exception if no items are found. Since you probably don't want your program to terminate for such a simple query, you're usually better off using either the standard LINQ FirstOrDefault or the DevForce extension FirstOrNullEntity. FirstOrDefault will return the first item or its default value. For a reference type such as an entity the default value is null (Nothing in VB). It's often easier to work with null entities in DevForce, so if you instead use FirstOrNullEntity either the first item matching the selection criteria is returned, or the entity type's null entity. As an immediate execution query, you must use AsScalarAsync to execute this query in Silverlight. C# Employee emp = manager.Employees.FirstOrNullEntity(e => e.City == "Moscow"); // ... or ... var op = manager.Employees.AsScalarAsync().FirstOrNullEntity(e => e.City == "Moscow"); VB Dim emp As Employee = manager.Employees.FirstOrNullEntity(Function(e) e.City = "Moscow") ' ... or ... Dim op = manager.Employees.AsScalarAsync().FirstOrNullEntity(Function(e) e.City = "Moscow") First vs. Single The LINQ Single operator returns the one and only element matching the selection criteria. If multiple elements match the criteria, it throws. If no elements match the criteria, it throws. This isn't some diabolical DevForce design, these are the rules of LINQ. If you do decide to use Single, it's usually best to use either SingleOrDefault or for async only, the DevForce extension SingleOrNullEntity, to ensure that the query won't fail if no item is returned. First vs. Take(1) The LINQ Take operator is usually used to take one or more items. You can use Skip with Take to skip items before taking; this is how paging is done. Take is not an immediate execution query, which can be good news in some environments, and doesn't use AsScalarAsync when executed asynchronously. It also always returns an IEnumerable<T>, so even a Take(1) will return an IEnumerable with the element. If no items matched the criteria then an empty enumeration is returned. Query using an IN clause If you've searched in vain for the LINQ equivalent to the SQL "In" clause, you can stop worrying. LINQ uses the Contains operator to implement a query with search criteria for a value in a list. (This is usually translated to a SQL "In" clause by the Entity Framework when the SQL is generated.) C# var countryNames = new List<string> {"UK", "France", "Germany"}; var query = manager.Customers .Where(c => countryNames.Contains(c.Country)); VB Dim countryNames = New List(Of String) From {"UK", "France", "Germany"} Dim query = manager.Customers.Where(Function(c) countryNames.Contains(c.Country)) There is one caveat here, however. Your contains list should be a List<T>, where "T" is a numeric type, a string, a DateTime or a GUID. Why this restriction? The list has to meet DevForce's requirements for known types. DevForce will automatically recognize these lists as known types without any extra effort on your part. If you need some other List then you will need to ensure it can be used in n-tier deployments. You also can't use an array, for example using string[] above will fail in an n-tier deployment. This is due to an arcane data contract naming issue, so don't say we didn't warn you.
Query without using LINQ In addition to the EntityQuery which supports the entire .NET LINQ syntax stack, DevForce provides several other query types that do not support LINQ syntax. These are the EntityKeyQuery, PassthruEsqlQuery and StoredProcQuery types for queries using EntityKeys, Entity SQL and stored procedures, respectively. Like the EntityQuery, these types all implement DevForces IEntityQuery interface. Unlike the EntityQuery<T>, these query types are not composable, meaning that additional clauses cannot be tacked onto them to further restrict or project the query results into another form. In addition, the PassthruESQLQuery and the StoredProcQuery types do not provide in- memory querying capabilities. What these queries do provide that the EntityQuery<T> does not is that they may offer, depending on the use case, better integration with existing database constructs, special functionality or improved performance. However, in most cases the EntityQuery<T> is usually a better choice, because of its greater flexibility. Sample PassthruEsqlQuery C# PassthruEsqlQuery query = new PassthruEsqlQuery(typeof(Employee), "SELECT VALUE e FROM Employees AS e Where e.EmployeeID < 10"); IEnumerable results = _em1.ExecuteQuery(query); VB Dim query As New PassthruEsqlQuery(GetType(Employee), _ "SELECT VALUE e FROM Employees AS e Where e.EmployeeID < 10") Dim results As IEnumerable = _em1.ExecuteQuery(query) Sample StoredProcQuery C# QueryParameter param01 = new QueryParameter("EmployeeID",1); QueryParameter param02 = new QueryParameter("Year",1996); StoredProcQuery query = new StoredProcQuery(typeof(Order)); query.Parameters.Add(param01); query.Parameters.Add(param02); // Note that a FunctionImport must be defined in the Entity Model query.ProcedureName = "OrdersGetForEmployeeAndYear"; _em1.ExecuteQuery(query); VB Dim param01 As New QueryParameter("EmployeeID", 1) Dim param02 As New QueryParameter("Year", 1996) Dim query As New StoredProcQuery(GetType(Order)) query.Parameters.Add(param01) query.Parameters.Add(param02) ' Note that a FunctionImport must be defined in the Entity Model query.ProcedureName = "OrdersGetForEmployeeAndYear" _em1.ExecuteQuery(query)
Query by EntityKey
The EntityKey of an entity defines the unique identity of an entity. You can query by EntityKey using the EntityKeyQuery. The EntityKeyQuery Because an EntityKey by definition uniquely identifies a single entity, the EntityKeyQuery is optimized in a way that other query types cannot be. The EntityManager, given an EntityKey, can determine by looking in its entity cache whether or not an entity with this key has already been fetched from the database. For other query types, DevForce uses its query cache to determine whether a query has already been executed. The determination of whether a given query has already been executed against a database is a DevForce performance enhancement. Even with an EntityKeyQuery, this optimization can be suppressed by using the DataSourceOnly QueryStrategy. Creating an EntityKeyQuery There are two basic ways to create an EntityKeyQuery: with an EntityKey, or with a list of EntityKeys called an EntityKeyList. You might wonder how to obtain an EntityKey since it's a property of an Entity (through its EntityAspect). You can construct an EntityKey if you know the entity type and the key values. For example, if you want to build an EntityKey for Employee 1, you could do the following: C# var key = new EntityKey(typeof(Employee), 1); VB Dim key = New EntityKey(GetType(Employee), 1) You can then build the EntityKeyQuery for this key. One easy way is to use the helper method ToKeyQuery: C# var query = key.ToKeyQuery(); VB Dim query = key.ToKeyQuery() Or using the EntityKeyQuery constructor: C# var query = new EntityKeyQuery(key); VB Dim query = New EntityKeyQuery(key) Building an EntityKeyQuery from a list of EntityKeys is similar. An EntityKeyList is simply a strongly-typed collection of EntityKeys. All keys in the list must be for the same type or abstract type. C# var key1 = new EntityKey(typeof(Employee), 1); var key2 = new EntityKey(typeof(Employee), 2); var keyList = new EntityKeyList(typeof(Employee), new[] { key1, key2 }); VB Dim key1 = New EntityKey(GetType(Employee), 1) Dim key2 = New EntityKey(GetType(Employee), 2) Dim keyList = New EntityKeyList(GetType(Employee), { key1, key2 }) You can create the EntityKeyQuery from the list in familiar ways: C# var query = keyList.ToKeyQuery(); ... or var query = new EntityKeyQuery(keyList); VB Dim query = keyList.ToKeyQuery() '...or Dim query = New EntityKeyQuery(keyList) With the query in hand then you can then do many of the usual things you might do with a query. The EntityKeyQuery is not a LINQ query so not all features will be available to it, but you can set its QueryStrategy and EntityManager, and execute it via the ExecuteAsync or ExecuteQueryAsync methods. Because the EntityKeyQuery is not a generically typed class, its result is a simple IEnumerable. You can cast the result an IEnumerable<T>. C# var employees = entityManager.ExecuteQuery(query).Cast<Employee>(); VB Dim employees = entityManager.ExecuteQuery(query).Cast(Of Employee)() C# entityManager.ExecuteQueryAsync(query), op => { var items = op.Results.Cast<Employee>(); }); VB Dim employees = entityManager.ExecuteQuery(query).Cast(Of Employee)() Disadvantages of the EntityKeyQuery While the EntityKeyQuery has its uses, it has some pretty substantial shortcomings when compared with a standard LINQ query. The biggest of these is that the EntityKeyQuery is not composable. This means that we cannot apply any additional restrictions, projections, ordering etc. on these queries. We can of course perform all of the operations on the results of an EntityKeyQuery after the query query returns but the server will have still needed to perform the entire query. The second disadvantage of the EntityKeyQuery is that it is really only intended for small numbers of EntityKeys. The reason for this is that these methods are implemented so that they in effect create a large "IN" or "OR" query for all of the desired entities by key. The query expression itself can therefore become very large for large numbers of entities. Expressions that are this large will have performance impacts in both serialization as well as query compilation. For those cases where very large numbers of entities need to be refreshed, it is usually a better idea to write a "covering" query that is much smaller textually but returns approximately the same results. You may find that even though you return more entities than are needed with this covering query, the resulting overall performance is still better. Query by Id You might wonder how an EntityKeyQuery differs from a simple LINQ query by Id. For example, instead of: C# var key = new EntityKey(typeof(Employee), 1); var query = key.ToKeyQuery(); VB Dim key = New EntityKey(GetType(Employee), 1) Dim query = key.ToKeyQuery() ... we could instead have built an EntityQuery: C# var query = entityManager.Employees.Where(e => e.EmployeeID == 1); //.. more useful, use an EntityQuery with FirstOrNullEntity() var emp = entityManager.Employees.FirstOrNullEntity(e => e.EmployeeID == 1); VB Dim query = entityManager.Employees.Where(Function(e) e.EmployeeID = 1) '.. more useful, use an EntityQuery with FirstOrNullEntity() Dim emp = entityManager.Employees.FirstOrNullEntity(Function(e) e.EmployeeID = 1)
The primary difference to DevForce is that it doesn't know that the EntityQuery is querying only by the EntityKey value, and will thus treat the query as any other query in terms of optimization: it will look for the query in the QueryCache and if not present send the query to the datastore, even if the queried entity was already in the entity cache. With the EntityKeyQuery, DevForce will first search the entity cache for the requested entity, and only if not present send the query to the datastore. A second difference is that the LINQ query allows you to return a null entity if the requested entity was not found, while the EntityKeyQuery will return an empty enumeration. Another difference is that the EntityQuery is composable, so you can use all standard LINQ operators supported by the EntityQuery Entity SQL (ESQL) queries DevForce supports Entity SQL (ESQL) queries with its PassthruEsqlQuery() class. As with all other query types, PassthruEsqlQuery implements the IEntityQuery interface.
API There are several PassThruEsqlQuery costructor overloads: C# public PassthruEsqlQuery(Type returnType, String esql)
public PassthruEsqlQuery(Type returnType, Type queryableType, String esql)
public PassthruEsqlQuery(Type returnType, ParameterizedEsql parameterizedEsql)
public PassthruEsqlQuery(Type returnType, Type queryableType, ParameterizedEsql parameterizedEsql) VB Public Sub New(ByVal returnType As Type, ByVal esql As String)
Public Sub New(ByVal returnType As Type, ByVal parameterizedEsql As ParameterizedEsql) End Sub
Public Sub New(ByVal returnType As Type, ByVal queryableType As Type, ByVal parameterizedEsql As ParameterizedEsql) End Sub Simple queries The simplest calls require only a return type and a valid ESQL expression as show below: C# var query0 = new PassthruEsqlQuery(typeof(Customer), "SELECT VALUE c FROM Customers AS c Where c.Country == 'Brazil'"); var result0 = query.With(_em1).Execute().Cast<Customer>();
var query1 = new PassthruEsqlQuery(typeof(SalesOrderHeader), "SELECT VALUE SalesOrderHeader FROM SalesOrderHeaders AS SalesOrderHeader Where SalesOrderHeader.Customer.CustomerID < 10"); var results1 = q1.With(_em1).Execute().Cast<SalesOrderHeader>();
var query2 = new PassthruEsqlQuery(typeof(SalesPerson), "SELECT VALUE sp FROM SalesPersons AS sp Where sp.Bonus > 2000"); var results2 = query2.With(_em1).Execute().Cast<SalesPerson>(); VB Dim query0 = New PassthruEsqlQuery(GetType(Customer), _ "SELECT VALUE c FROM Customers AS c Where c.Country == 'Brazil'") Dim result0 = query.With(_em1).Execute().Cast(Of Customer)()
Dim query1 = New PassthruEsqlQuery(GetType(SalesOrderHeader), _ "SELECT VALUE SalesOrderHeader FROM SalesOrderHeaders AS SalesOrderHeader Where SalesOrderHeader.Customer.CustomerID < 10") Dim results1 = q1.With(_em1).Execute().Cast(Of SalesOrderHeader)()
Dim query2 = New PassthruEsqlQuery(GetType(SalesPerson), _ "SELECT VALUE sp FROM SalesPersons AS sp Where sp.Bonus > 2000") Dim results2 = query2.With(_em1).Execute().Cast(Of SalesPerson)() Note that because the PassthruEsqlQuery class is not generic all of the results from executing such a query must be cast to the appropriate result type. Queries with parameters Queries with parameters can also be used as shown below: C# var param = new QueryParameter("country", "Brazil"); var paramEsql = new ParameterizedEsql( "SELECT VALUE c FROM Customers AS c Where c.Country > @country", param); var query = new PassthruEsqlQuery(typeof(Customer), paramEsql); var result1 = query.With(_em1).Execute().Cast<Customer>();
param.Value = "Germany"; var result2 = query.With(_em1).Execute().Cast<Customer>(); VB Dim param = New QueryParameter("country", "Brazil") Dim paramEsql = New ParameterizedEsql( _ "SELECT VALUE c FROM Customers AS c Where c.Country > @country", param) Dim query = New PassthruEsqlQuery(GetType(Customer), paramEsql) Dim result1 = query.With(_em1).Execute().Cast(Of Customer)()
param.Value = "Germany" Dim result2 = query.With(_em1).Execute().Cast(Of Customer)() Note that the value of the parameter can be changed and the same query re-executed, returning different results. More complex queries So far all of the queries shown have involved queries where the source type or queryable type of the query is the same as the return type. If this is not the case then we need to pass in both types to the constructor. For example: C# var query1 = new PassthruEsqlQuery(typeof(Int32), typeof(Customer), "SELECT VALUE Count(c.CustomerType) FROM Customers AS c Where c.CustomerID < 10"); var result1 = query1.With(_em1).Execute().Cast<Int32>();
var query2 = new PassthruEsqlQuery(typeof(Decimal), typeof(SalesPerson), "SELECT VALUE Sum(sp.Bonus) FROM SalesPersons AS sp Where sp.Bonus > 2000"); var result2 = query2.With(_em1).Execute().Cast<Decimal>(); VB Dim query1 = New PassthruEsqlQuery(GetType(Int32), GetType(Customer), _ "SELECT VALUE Count(c.CustomerType) FROM Customers AS c Where c.CustomerID < 10") Dim result1 = query1.With(_em1).Execute().Cast(Of Int32)()
Dim query2 = New PassthruEsqlQuery(GetType(Decimal), GetType(SalesPerson), _ "SELECT VALUE Sum(sp.Bonus) FROM SalesPersons AS sp Where sp.Bonus > 2000") Dim result2 = query2.With(_em1).Execute().Cast(Of Decimal)() Additional notes When you use Entity SQL, youre responsible for formulating a query string that constitutes a valid query. If you goof, you wont know until you run it. A PassthruEsqlQuery can only be executed with the DataSourceOnly query strategy. DevForce does not currently support ESQL queries with anonymous projections. Stored procedure queries DevForce supports querying for entities using stored procedure queries. The need arises most frequently when we require the entities resulting from an extraordinarily complex query involving large volumes of intermediate data that are not themselves required on the client. One might imagine a multi-step query that touched several tables, performed multi-way joins, ordered and aggregated the intermediate results, and compared values with many thousands of records, all so as to return a handful of qualifying results. All of the other data were needed only to satisfy the query; the user wont see any of them and there is no point to transmitting them to the client. This is a clear case for a stored procedure because we can and should maximize performance by performing all operations as close to the data source as possible. Chances are that the entities returned by the stored procedure are entities we already know. That procedure could be just an especially resource-consuming query for Order entities that we retrieve and save in the usual way under normal circumstances. TheStoredProcQuery is perfect for this situation. We define such a query, identify Order as the query return type, and turn it loose on the database. We accept the sproc-selected Order objects and work with them in our typical merry way. Note that a stored procedure query, by its nature, must be executed by the database: we cant run it against the entity cache. So we may not invoke it while the application is running offline.
Suppose your data source includes a stored procedure named SalesByYear. It is defined as follows: (This example uses SQL Server TSQL, but any stored procedure than is supported via the Entity Framework will also be supported by DevForce). TSQL ALTER procedure "SalesbyYear" @Beginning_Date DateTime, @Ending_Date DateTime AS SELECT OrderSummary.ShippedDate, OrderSummary.id, "Order Subtotals".Subtotal, DATENAME(yy,ShippedDate) AS Year FROM OrderSummary INNER JOIN "Order Subtotals" ON OrderSummary.Id = "Order Subtotals".OrderSummaryId WHERE OrderSummary.ShippedDate Between @Beginning_Date And @Ending_Date Along with tables and views, stored procedures can be added to the model using the EDM Designer. Adding the stored procedure above results in the following Function element in the schema (SSDL) section of the Entity Model file: XML <Function Name="SalesbyYear" Schema="dbo" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion"> <Parameter Name="Beginning_Date" Type="datetime" Mode="In" /> <Parameter Name="Ending_Date" Type="datetime" Mode="In" /> </Function> To make this conveniently 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 also add a FunctionImport element to the conceptual model, using the EDM Designer (see http://msdn.microsoft.com/en-us/library/bb896231.aspx for more information on the mechanics of adding stored procedures to the Entity Model). The resulting FunctionImport would be defined as follows within the CSDL portion of the model: XML <FunctionImport Name="GetSalesByYear" EntitySet= "SalesByYearResults" ReturnType= "Collection(IdeaBladeTest1Model.EF.SalesbyYear)"> <Parameter Name="Beginning_Date" Type="DateTime" Mode="In" /> <Parameter Name="Ending_Date" Type="DateTime" Mode="In" /> </FunctionImport> DevForce will generate query and execute methods into your EntityManager for every function import in the conceptual model. In a Silverlight application, youd call the query method to construct a query and then execute that query asynchronously; the execute method cannot be used because it will execute the query synchronously. C# public IEnumerable<IdeaBladeTest1Model.SalesbyYear> GetSalesByYear( Nullable<DateTime> Beginning_Date, Nullable<DateTime> Ending_Date) {} public StoredProcQuery GetSalesByYearQuery( Nullable<DateTime> Beginning_Date, Nullable<DateTime> Ending_Date) {} VB Public Function GetSalesByYear(ByVal Beginning_Date? As Date, _ ByVal Ending_Date? As Date) As _ IEnumerable(Of IdeaBladeTest1Model.SalesbyYear) End Function Public Function GetSalesByYearQuery(ByVal Beginning_Date? As Date, _ ByVal Ending_Date? As Date) As StoredProcQuery End Function Having done all of that in your Entity Model, you can now use the resultant methods as shown below: C# var _em1 = new IdeaBladeTest1Entities(); DateTime dt1 = DateTime.Parse("1/1/1990"); DateTime dt2 = DateTime.Parse("1/1/2000"); var results = _em1.GetSalesByYear(dt1, dt2); // Or asynchronously IEnumerable results; var q = _em1.GetSalesByYearQuery(dt1, dt2); var op = _em1.ExecuteQueryAsync(q); op.Completed += (o, e) => { results = e.Results; }; VB Dim _em1 = New IdeaBladeTest1Entities() Dim dt1 As Date = Date.Parse("1/1/1990") Dim dt2 As Date = Date.Parse("1/1/2000") Dim results = _em1.GetSalesByYear(dt1, dt2) ' Or asynchronously Dim results As IEnumerable Dim q = _em1.GetSalesByYearQuery(dt1, dt2) Dim op = _em1.ExecuteQueryAsync(q) AddHandler op.Completed, Sub(o, e) results = e.Results Stored procedure entity navigation Dot Navigation is a bit tricky for entities that are defined only by a stored procedure. Navigating to these entities via navigation properties is not supported, since EF itself does not support mapping functions for an entity to a query function (as it does for insert, update and delete functions). You can of course define custom properties within your entities to perform this navigation via a stored procedure query. Navigating from these stored-procedure backed entities to table-backed entities is not a problem. Query asynchronously DevForce provides the ability to query asynchronously for two reasons: 1) The Silverlight CLR (common language runtime) environment does not permit synchronous web service calls. 2) Even in those environments where synchronous API's are supported, there are many use cases where performance can be improved via the use of an asynchonous query. While an overview of asynchonous programming in DevForce discusses the specific asynchronous API's provided in the context of querying. Please review the overview document for context if necessary. EntityQueryOperation<T> and EntityQueriedEventArgs<T> The return value from any call to either a EntityQueryExtensions.ExecuteAsync or a EntityManager.ExecuteQueryAsync is an instance of EntityQueryOperation<T>. This operation result can be either be accessed directly within the method's callback logic or via the operation's Completed event. In the case of the Completed event; the event delegate gets passed a EntityQueriedEventArgs<T> parameter. Regardless of whether you are working with an EntityQueryOperation<T> or an EntityQueriedEventArgs<T>, the following read only properties are provided in addition to those inherited from the BaseOperation or AsyncEventArgs base classes. Please see Program asynchronously for more detail on the standard DevForce asynchronous model and the properties available there. The following discussion is only about those additional properties that are available during an asynchonous query. Property Property Type Description EntityQuery IEntityQuery<T> The requested query Results IEnumerable<T> The results of the query. ChangedEntities IList<Object> The list of every entity that was either added or modified in the EntityManager's cache as a result of this query. The ChangedEntities list may differ from the Results property since the Results will include only those entities directly queried, and not entities fetched due to query inversion or use of an "Include". WasFetched bool Whether the operation actually required a trip to the database. Many queries can be completed without having to go to the database. ResolvedFetchStrategy FetchStrategy The FetchStrategy actually used to process the query. This is really only useful when an 'optimized' FetchStrategy was stipulated when executing the query. What is returned here is the query strategy that 'optimized' determined was most appropriate. Note that an exception is thrown when accessing the Results or ChangedEntities properties if the asynchronous operation was either cancelled or failed, since the result is undefined. Example of the ExecuteAsync() extension method and EntityManager.ExecuteQueryAsync() DevForce provides two primary ways to perform asynchronous queries. The first is to use any of the ExecuteAsync() extension methods or EntityManager.ExecuteQueryAsync() method overloads. For example: C# var query = _em1.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName);
// Using IEntityQuery.ExecuteAsync with lambda syntax: query.ExecuteAsync( op => { IEnumerable<Customer> customers = op.Results; });
// Using EntityManager.ExecuteQueryAsync with lambda syntax: _em1.ExecuteQueryAsync(query, op => { IEnumerable<Customer> customers = op.Results; });
// Using IEntityQuery.ExecuteAsync with Completed event syntax: var op = query.ExecuteAsync(); op.Completed += (sender, eventArgs) => { IEnumerable<Customer> customers = eventArgs.Results; };
// Using EntityManager.ExecuteQueryAsync with Completed event syntax: var op = _em1.ExecuteQueryAsync(query); op.Completed += (sender, eventArgs) => { IEnumerable<Customer> customers = eventArgs.Results; }; VB Dim query = _em1.Customers.Where(Function(c) c.ContactTitle = "Sales Representative").OrderBy(Function(c) c.Compa
' Using IEntityQuery.ExecuteAsync with lambda syntax: query.ExecuteAsync(Sub(op) Dim customers As IEnumerable(Of Customer) = op.Results)
' Using EntityManager.ExecuteQueryAsync with lambda syntax: _em1.ExecuteQueryAsync(query, Sub(op) Dim customers As IEnumerable(Of Customer) = op.Results)
' Using IEntityQuery.ExecuteAsync with Completed event syntax: Dim op = query.ExecuteAsync() AddHandler op.Completed, Sub(sender, eventArgs) Dim customers As IEnumerable(Of Customer) = eventArgs.Results
' Using EntityManager.ExecuteQueryAsync with Completed event syntax: Dim op = _em1.ExecuteQueryAsync(query) AddHandler op.Completed, Sub(sender, eventArgs) Dim customers As IEnumerable(Of Customer) = eventArgs.Results Example of IEntityQuery.AsScalarAsync() For what are termed "scalar queries", i.e. queries that return an immediate single result, the mechanism is slightly different and uses the IEntityQuery.AsScalarAsync() extension method. An example is shown below: C# var query = _em1.Customers .Where(c => c.ContactTitle == "Sales Representative") .OrderBy(c => c.CompanyName);
// Using lambda syntax query.AsScalarAsync().FirstOrNullEntity( op => { Customer cust = op.Result; });
// Using Completed event syntax var op = query.AsScalarAsync().FirstOrNullEntity(); op.Completed += (sender, eventArgs) => { Customer cust = eventArgs.Result; }; VB Dim query = _em1.Customers.Where(Function(c) c.ContactTitle = _ "Sales Representative").OrderBy(Function(c) c.CompanyName)
' Using lambda syntax query.AsScalarAsync().FirstOrNullEntity(Sub(op) Dim cust As Customer = op.Result)
' Using Completed event syntax Dim op = query.AsScalarAsync().FirstOrNullEntity() AddHandler op.Completed, Sub(sender, eventArgs) Dim cust As Customer = eventArgs.Result
Control query execution What is a QueryStrategy? QueryStrategy is a class in the IdeaBlade.EntityModel namespace. Every query has a QueryStrategy property that returns a instance of the QueryStrategy type. This property has a default value of null, but can be set on any query as follows: C# EntityQuery<Order> query01 = myEntityManager.Orders; query01.QueryStrategy = QueryStrategy.DataSourceThenCache; VB Dim query01 As EntityQuery(Of Order) = myEntityManager.Orders query01.QueryStrategy = QueryStrategy.DataSourceThenCache In addition, every EntityManager has a DefaultQueryStrategy property that is used whenever you do not explicitly specify the query strategy you want to use with a particular query. The DefaultQueryStrategy is also used whenever you explicitly set a query's QueryStrategy property to null (Nothing in VB). By default the DefaultQueryStrategy has a value of QueryStrategy.Normal but you can also set it as follows: C# myEntityManager.DefaultQueryStrategy = QueryStrategy.DataSourceOnly; VB myEntityManager.DefaultQueryStrategy = QueryStrategy.DataSourceOnly 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 under which it is run. The QueryStrategy class is immutable and has four properties that uniquely define it:
QueryStrategy.FetchStrategy :The FetchStrategy controls where DevForce looks for the requested data: in the cache, in the datasource, or in some combination of the two.
QueryStrategy.MergeStrategy ::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.
QueryStrategy.InversionMode :::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).
QueryStrategy.TransactionSettings ::::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 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. Pre-Defined QueryStrategies Every QueryStrategy combines a FetchStrategy, a MergeStrategy, and a InversionMode. Since there are five FetchStrategies, five MergeStrategies, and four InversionModes, there are potentially 100 versions of QueryStrategy, even keeping the TransactionSettings constant. However, in practice, a much smaller set of QueryStrategies suffices for the great majority of purposes. DevForce has identified five of them as being of particular significance, enshrining them as static (Shared in VB) properties of the QueryStrategy class. These pre-defined QueryStrategies combine FetchStrategy, MergeStrategy, and InversionMode strategies as shown in the table below: Fetch and merge strategies of the common query strategies QueryStrategy Fetch Strategy Merge Strategy InversionMode Normal Optimized PreserveChanges Try CacheOnly CacheOnly (Not Applicable) (Not Applicable) DataSourceOnly DataSourceOnly OverwriteChanges Off DataSourceOnlyWithInversion DataSourceOnly OverwriteChanges On DataSourceThenCache DataSourceThenCache OverwriteChanges Try Heres how you assign a pre-defined QueryStrategy: C# query.QueryStrategy = QueryStrategy.DataSourceThenCache; VB query.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 instance. Immutable meaning that we can get the component fetch and merge strategies but we cannot reset them. Heres an example of the creation and assignment of a custom QueryStrategy: C# QueryStrategy aQueryStrategy = new QueryStrategy(FetchStrategy.DataSourceThenCache, MergeStrategy.PreserveChanges, QueryInversionMode.On); VB Dim aQueryStrategy As New QueryStrategy( _ FetchStrategy.DataSourceThenCache, _ MergeStrategy.PreserveChanges, _ QueryInversionMode.On) There is another, often more useful, method of creating a custom QueryStrategy, via the use of one of the With overloads provided by the QueryStrategy class. These With methods allow you to create a new QueryStrategy based on an existing QueryStrategy with one of the properties changed. For example: C# QueryStrategy queryStrategy1 = QueryStrategy.Normal.With(FetchStrategy.DataSourceOnly); QueryStrategy queryStrategy2 = QueryStrategy.DataSourceOnly.With(MergeStrategy.PreserveChangesUpdateOriginal); QueryStrategy queryStrategy3 = queryStrategy1.With(QueryInversionMode.Off); VB Dim queryStrategy1 As QueryStrategy = _ QueryStrategy.Normal.With(FetchStrategy.DataSourceOnly) Dim queryStrategy2 As QueryStrategy = _ QueryStrategy.DataSourceOnly.With(MergeStrategy.PreserveChangesUpdateOriginal) Dim queryStrategy3 As QueryStrategy = _ queryStrategy1.With(QueryInversionMode.Off) 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 EntityManagers 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 PMs DefaultQueryStrategy property. For a given query, you could still use any desired QueryStrategy by explicitly specifying a different one. When to use the different QueryStrategies For most users, most of the time, the DevForce defaults are perfect: Satisfy a query from the entity cache whenever possible; When a trip to the data source is found necessary, resolve any conflicts that occur between incoming data and data already cache by giving the local version priority; and Perform query inversion as needed; if needed and undoable, revert to a DataSourceOnly FetchStrategy. Your choice of a non-default strategy can be driven by a variety of things. For example, suppose your application supports online concert ticket sales. Your sales clerks need absolutely up-to-date information about what seats are available at the time they make a sale. In that use case, it will be essential to direct your query for available seats against the data source, so a FetchStrategy of DataSourceOnly might be in order. In code to handle concurrency conflicts, one might need a QueryStrategy with a MergeStrategy of PreserveChangesUpdateOriginal to make an entity in conflict savable. (The data source version of the conflicted entity would only be retrieved and used to partially overwrite the cache version after the concurrency conflict had been resolved by some predetermined strategy.) You can and will think of your own reasons to use different combinations of FetchStrategy, MergeStrategy, and InversionMode. Just ask yourself, for a given data retrieval operation, whether the data in the cache is good enough, or you need absolutely current data from the data source. Then ask yourself how you want to resolve conflicts between data already cached and duplicate incoming data. Then consider the process DevForce will use to satisfy the query and make sure it will have the data it needs to give you a correct result. DevForce gives you the flexibility to set the behavior exactly as need it. Changing a QueryStrategy for a single query execution You may find yourself with an existing IEntityQuery object that you dont 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 on IEntityQuery, called With(), that permits you to this. ( 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.) Note that this is a different method from the QueryStrategy.With() method mentioned earlier. 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. C# IEntityQuery<Customer> query00 = _em1.Customers .Where(c => c.CompanyName.ToLower().StartsWith("a")); query00.QueryStrategy = QueryStrategy.DataSourceOnly; // The With() call in the right-hand side of the following statement // specifies a query that is materially different from query0, in // that it has a different QueryStrategy associated with it. // Accordingly, the right-hand side of the statement will return // a new query: IEntityQuery<Customer> query01 = query00.With(QueryStrategy.CacheOnly); // Because the content of the With() call in the right-hand side // of the following statement doesn't result in a modification // of query0, the right-hand side will return a reference to // query0 rather than a new query. IEntityQuery<Customer> query02 = query00.With(QueryStrategy.DataSourceOnly); // If you want to be certain you get a new query, use Clone() // rather than With(): EntityQuery<Customer> query03 = (EntityQuery<Customer>)query00.Clone(); query03.QueryStrategy = QueryStrategy.DataSourceOnly; VB Dim query00 As IEntityQuery(Of Customer) = _ _em1.Customers.Where(Function(c) c.CompanyName.ToLower().StartsWith("a")) query00.QueryStrategy = QueryStrategy.DataSourceOnly
' The With() call in the right-hand side of the following statement ' specifies a query that is materially different from query0, in ' that it has a different QueryStrategy associated with it. ' Accordingly, the right-hand side of the statement will return ' a new query: Dim query01 As IEntityQuery(Of Customer) = _ query00.With(QueryStrategy.CacheOnly)
' Because the content of the With() call in the right-hand side ' of the following statement doesn't result in a modification ' of query0, the right-hand side will return a reference to ' query0 rather than a new query. Dim query02 As IEntityQuery(Of Customer) = _ query00.With(QueryStrategy.DataSourceOnly)
' If you want to be certain you get a new query, use Clone() ' rather than With(): Dim query03 As EntityQuery(Of Customer) = _ CType(query00.Clone(), EntityQuery(Of Customer)) query03.QueryStrategy = QueryStrategy.DataSourceOnly Control where the query executes DevForce uses the FetchStrategy property of the QueryStrategy class to control where the query executes: in the cache, in the datasource, or in some combination of the two. FetchStrategies Five FetchStrategies are available in DevForce: Strategy Action CacheOnly Apply this query against the cache only, returning references only to entities already there. Do not consult the data source. (Note that this query leaves the cache unchanged.) DataSourceOnly Retrieve matching entries from the datasource into the entity cache. Return references only to those entities retrieved from the the data source. A result set returned from a query using this FetchStrategy would not include locally added entities that had not yet been persisted to the data source. DataSourceThenCache First retrieve matching entries from the datasource into the entity cache. Discard all references to entities retrieved in this step. Resubmit the same query against the updated cache. Return references only to entities matched by this second, CacheOnly query. DataSourceAndCache First retrieve matching entries from the datasource into the entity cache. Retain references to entities retrieved in this step. Resubmit the same query as CacheOnly. Combine (union) the references obtained in this second, CacheOnly query with those obtained in the data source retrieval step. Optimized Check the query cache to see if the current query has previously been submitted (and, if necessary, inverted) successfully. If so, satisfy the query from the entity cache, and skip the trip to the datasource. If the query cache contains no query matching or encompassing the current query, then determine if all entities needed to satisfy the query correctly from the cache can be retrieved into the cache. If so, apply the DataSourceThenCache FetchStrategy. Otherwise, apply the DataSourceOnly FetchStrategy. See the discussion on query inversion for more detail. 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. Merge query results into the entity cache DevForce uses a MergeStrategy to determine how to reconcile potential conflicts between entities that are being merged into the cache with the entities that are already present in the cache. For example, you may have an existing version of an entity in the cache, and are trying to merge with a "new" version of the same entity (the two entities have the same primary key). This "new" entity may be the result of a query, a call to RefetchEntities, or may be an entity that is being imported from another workflow using a different EntityManager. If one or both entities have been changed, the MergeStrategy is used to determine which version of the entity is stored in cache. MergeStrategy DevForce supports five different MergeStrategies : PreserveChanges, OverwriteChanges, PreserveChangesUnlessOriginalObsolete, PreserveChangesUpdateOriginal, and NotApplicable. Their meanings are shown in the table below. 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 objects 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. MergeStrategies: Strategy Action when cached entity has pending changes PreserveChanges Preserves the state of the cached entity. OverwriteChanges Overwrites the cached entity with data from the data source. Sets the EntityState of the cached entity to Unchanged. PreserveChangesUnless OriginalObsolete Preserves the values in the Current state of the cached entity, if its Original state matches the state retrieved from the datasource. If the state as retrieved from the datasource differs from that found locally in the Original set of property values, this indicates that the entity has been changed externally by another user or process. In this case (with this MergeStrategy), DevForce overwrites the local entity, 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. MergeStrategy behavior What happens during the merge of a data source entity and a cached entity depends upon the answers to three crucial questions: 1. Is the entity current or obsolete? 2. How has it changed? 3. Is the entity represented in the data source? Is the entity current or obsolete relative to the data source? We compare the cached entitys concurrency column property value to that of its data source entity. If the two are the same, the cached entity is current; if they differ, the cached entity is obsolete. As it happens, the cached entity has two concurrency column property values, a current one and an original one. The value of the concurrency column in the current version is meaningless. Its the value of the concurrency column in the original version that counts. Every DevForce entity has an original version and a current version of its persistent state. We can get to one or the other by means of a static GetValue() method defined on the EntityProperty class. For example, the following code gets the original value (as retrieved from the database) for the RequiredDate property of a particular Order instance: C# DomainModelEntityManager mgr = DomainModelEntityManager.DefaultManager; anOrder = mgr.Orders.Where(o => o.OrderID == 10248).First(); Datetime reqdDate = Order.PropertyMetadata.RequiredDate.GetValue( anOrder, EntityVersion.Current); VB DomainModelEntityManager.DefaultManager anOrder = mgr.Orders.Where(Function(o) o.OrderID = 10248).First() Dim reqdDate As Datetime = Order.PropertyMetadata.RequiredDate.GetValue( anOrder, EntityVersion.Current) Both of the following statements get the current value for the same property: C# reqdDate = Order.PropertyMetadata.RequiredDate.GetValue( anOrder, EntityVersion.Current);
reqdDate = anOrder.RequiredDate; // same as above (but simpler!) VB reqdDate = Order.PropertyMetadata.RequiredDate.GetValue(anOrder, EntityVersion.Current)
reqdDate = anOrder.RequiredDate ' same as above (but simpler!) Again, DevForce and the Entity Framework determine if our cached entity is current or obsolete based on the original version of the property value. How has it changed? The merge action depends upon whether the entity was added, deleted, or changed since we set its original version. The entitys EntityState property tells us if and how it has changed. Is the entity represented in the data source? If there is a data source entity that corresponds to the cached entity, we may use the data from data source entity to change the cached entity in some way. If we dont find a matching data source entity, we have to decide what to do with the cached entity. Maybe someone deleted the data source entity in which case we might want to discard the cached entity. If we, on the other hand, we want to save the cached entity, well have to insert it into the data source rather than update the data source. Merging when the entity is in the data source Well look at each strategy and describe the outcome based on (a) whether or not the cached entity is current and (b) the entitys EntityState. If the entity is Unchanged, we always replace both its original and current versions with data from the data source entity. Our remaining choices are evident in the following table. Merge strategy consequences for a changed cached entity that exists in the data source. Merge Strategy Current Added Deleted Detached Modified Post Current PreserveChanges Y NC NC NC NC Y N NC NC NC NC N OverwriteChanges Y or N OW OW OW OW Y PreserveChangesUnless OriginalObsolete Y ---- NC NC NC Y N OW OW OW OW Y PreserveChangesUpdateOriginal Y or N NC NC NC NC Y NC = No change; preserve the current version values of the cached entity OW = Overwrite the cached entitys 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 entitys EntityState may change after the merge. It will be marked Unmodified after merge with OverwriteChanges. It will be marked Unmodified after merge with PreserveChangesUnlessOriginalObsolete if the entity is obsolete. Note that deleted and detached entities are resurrected in both cases. An added cached entity must be deemed obsolete if it already exists in the data source. 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. We will not be able to insert that entity into the data source; well 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: EntityState after merge. Merge Strategy Current Added Deleted Detached Modified PreserveChanges Y or N A D Dt M OverwriteChanges Y or N U U U U PreserveChangesUnless OriginalObsolete Y --- D Dt M N U U U U PreserveChangesUpdateOriginal Y or N M D Dt M A = Added D = Deleted Dt = Detached M = Modified U = Unchanged The merge may change the original version of a changed cached entity to match the data source values. PreserveChanges never touches the original version. The original version is always changed with the OverwriteChanges strategy. It is reset with the PreserveChangesUnlessOriginalObsolete strategy if (and only if) the entity is obsolete.. PreserveChangesUpdateOriginal updates the original version (but not the current version!) if the entity is obsolete. This step ensures that the cached entity appears current while preserving the pending changes. These effects are summarized in the following table: Merge strategy effect on the original version of the cashed entity. Merge Strategy Current Added Deleted Detached Modified PreserveChanges Y or N NC NC NC NC OverwriteChanges Y or N OW OW OW OW PreserveChangesUnless OriginalObsolete Y ---- NC NC NC N OW OW OW OW PreserveChangesUpdateOriginal Y or N OW OW OW OW Merging when the cached entity is not in the data source We begin by considering cached entities that are unchanged. If the query applied to the cache returns an unchanged entity, X, and the query applied to the data source did not return its mate, we can safely assume that X was deleted after we fetched it. We can remove X from the cache. We turn next to changed cached entities where we must distinguish between a query that tests only for the primary key and one that tests for something other than the primary key. If the query tests for anything other than the primary key, we can draw no conclusions from the fact that a cached entity was not found in the database. For what does it mean if we have an employee named "Sue" in cache and we dont 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 dont find that employee in the data source, we can be confident of a simple interpretation. 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. A business object must have unique identity so if it isnt 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 cant 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 entitys 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 the entity from the cache. 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.
On the other hand, if the cached entity is new (Added), we dont 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 Added. An update would fail because there is no data source entity to update. In summary: Merge strategy consequences for a changed cached entity that does not exist in the data source. Merge Strategy Added Modified PreserveChanges A M OverwriteChanges A R PreserveChangesUnlessOriginalObsolete A R PreserveChangesUpdateOriginal A A A = Added M = Modified R = Removed DataSourceOnly subtleties We may get a nasty surprise if we use a DataSourceOnly or DataSourceThenCache query with other than the OverwriteChanges merge strategy. Consider the following queries using the PreserveChanges merge strategy. Suppose we hold the "Nancy" employee in cache. We change her name to "Sue" and then search the database for all Employees with first names beginning with S. We will not get "Sue" because she is still "Nancy" in the database. Suppose we search again but this time we search for first names beginning with N. This time we get "Sue". That will confuse the end user but it is technically correct because the Sue in cache is still "Nancy" in the database. 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".
Invert a query Inverting a query is accomplished through query inversion. Query inversion conveys a process by which we try to insure that any data queried from a backend datasource can also be queried, with the same results, by executing the same query against the local entity cache. This is difficult to insure under two conditions 1. Where a query involves a projection. ( Select or SelectMany) 2. Where a query involves a subquery. InversionMode DevForce is able to automatically perform query inversion on queries that involve subqueries, but must rely on the developer's assistance in inverting projection queries via judicious use of Include clauses. Because of this it is often useful to construct queries using subqueries instead of projections in order to gain access to DevForce's automatic query inversion logic. An example of query inversion follows: 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 of the current-year Orders that were placed by these customers. Another way of understanding the idea 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. A handy side effect of query inversion is that in practice there is a reasonably good 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. 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: Inversion Mode Implicit Instructions to DevForce On Attempt to retrieve, from the datasource and into the cache, entities other than the targeted 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 targeted type into the cache. Try Attempt to retrieve, from the datasource and into the cache, all entities other than the targeted type which are needed for correct processing of the query. However, if this attempt fails, just retrieve the entities of the directly targeted type, and do not throw an exception. Manual Dont attempt to invert the current query; but act as if it were successfully inverted (if it needed to be). You (the developer) should only use this InversionMode when you are prepared to guarantee, on your own, that the entity cache contains (or will contain, after the DataSource portion of the query operation) all the necessary related objects to return a correct result if submitted against the cache. Normally you would make good on this guarantee by performing other data retrieval operations (prior to the one in question) to retrieve the necessary related data; or by including calls to the Include() extension method in the current query, sufficient to retrieve the necessary related data. The default InversionMode is Try, and this will likely be your choice for most queries. You should use On only if your application absolutely depends upon the related entities being brought into the cache by your query, and you should include exception handling in case the strategy fails. Choose the Off setting if you only want the targeted entries retrieved into the cache. Be sure you choose a compatible FetchStrategy. For queries that DevForce can successfully invert, the InversionModes of Try and On will yield the same end state: the query will be cached, and all related objects necessary to permit future satisfaction of the query entirely from the cache will be assumed to be present in the cache. If you use the InversionMode of Manual properly that is, you take care to see that the necessary related objects get retrieved into the cache by some means or another before the query is submitted then it, too, will produce the same ending state as the Try and On settings. Queries that cannot be automatically inverted The following types of queries cannot be automatically inverted: A query that returns a scalar result. This includes all aggregate queries (Count, Sum, Avg, etc.). Note that this group includes the example mentioned earlier in this discussion: Give me the count of Customers located in Idaho. C# var query02 = _em1.Orders.Select(o => o.FreightCost).Sum(); VB Dim query02 = _em1.Orders.Select(Function(o) o.FreightCost).Sum() A query whose return type is a single element. These include queries that call .First(), .Last(), and .Single() C# var query03 = _em1.Products.OrderByDescending(c => c.ProductName).FirstOrNullEntity(); {{/code}} VB Dim query03 = _em1.Products.OrderByDescending( _ Function(c) c.ProductName).FirstOrNullEntity() A query whose return type is different from the type contained in the collection first referenced. C# var query04 = _em1.Customers .Where(c => c.Country == "Argentina") .SelectMany(c => c.Orders); VB Dim query04 = _em1.Customers _ .Where(Function(c) c.Country = "Argentina") _ .SelectMany(Function(c) c.Orders) Match the semantics of cache queries to database queries There are several interesting issues that arise when DevForce tries to execute the same EntityQuery against both a backend datastore using the Entity Framework and its own local EntityManager cache. Internally DevForce uses LINQ to Entities to query entities on the server but uses LINQ to Objects (LINQ against the .NET CLR) to query these same entities from the local cache. Unfortunately, there are subtle semantic differences between LINQ to Entities and LINQ to Objects. Some of these differences have to do with the way that LINQ to Entities itself is defined and some of these differences have to do with the backend SQL database implementation that LINQ to Entities is communicating with. Ideally we want to match the semantics of cache queries to database queries. QueryStrategy and CacheQueryOptions In general, it is DevForce's goal to insure that the execution of a query against the local EntityManager cache is interpreted identically to that same query run against Linq to Entities on the server. This is accomplished by modifying the way that standard LINQ to Objects queries are interpreted when run against the DevForce EntityManager cache. The goal is to mirror LINQ to Entities semantics for these queries. While much of this can be performed automatically by DevForce, there is some logic that is dependent on the settings of whatever backend SQL database is being used on the server. The IdeaBlade.EntityModel.CacheQueryOptions property contains the settings that allow a developer to inform DevForce of any database settings that may effect LINQ to Entities behavior, and that will therefore require DevForce to modify the way it executes those same queries when run against the local cache as well. Typically, within a single application, these settings will be the same for all queries and therefore the CacheQueryOptions class offers a static (shared in VB) 'Default' property that will be used by every query unless explicitly overridden on that query's QueryStrategy. By default this property returns an instance of CacheQueryOptions that is appropriate for most standard SQL Server implementations. (See CacheQueryOptions.DefaultSqlServerCompatibility) Instances of the CacheQueryOptions class are immutable and can be created via the following constructor C# public CacheQueryOptions(StringComparison stringComparison, bool useSql92CompliantStringComparison, GuidOrdering guidOrdering) VB public CacheQueryOptions(StringComparison stringComparison, _ Boolean useSql92CompliantStringComparison, GuidOrdering guidOrdering) Each of the parameters to the constructor is described below: stringComparison: Because many SQL databases are configured to allow queries against string columns to be performed in a case insensitive manner, it is necessary to inform DevForce that it should mirror this behavior when performing queries against its local cache. Note that this is different than standard Linq to Objects (.NET CLR behavior) which is to perform case sensitive comparisons. The default for this is StringComparison.OrdinalIgnoreCase. (i.e. case insensitive comparisons). useSql92CompliantStringComparison: The ANSI/ISO SQL-92 specification (Section 8.2, <Comparison Predicate>, General rules #3) describes how to compare strings with spaces. The ANSI standard requires padding for the character strings used in comparisons so that their lengths match before comparing them. The padding directly affects the semantics of WHERE and HAVING clause predicates and other string comparisons. For example, ANSI- SQL considers the strings 'abc' and 'abc ' to be equivalent for most comparison operations. This is the behavior that DevForce mimics for local cache queries when this value is set to true, otherwise normal .NET CLR comparison semantics are used (i.e. no padding). The default value for this flag is true. guidOrdering: SQL Server currently sorts 'Guids' according to different rules than those used by the .NET CLR. This setting allows the local cache to match SQL Server's sorting behavior in any LINQ query that involves grouping or ordering of 'Guid's. The default for this property is GuidOrdering.SqlServer. The only other option currently available is GuidOrdering.CLR. Transactional queries DevForce query requests are atomic. This means that even when a query request resolves into multiple SQL queries, they will 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 involves 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. Setting the transaction isolation level on individual commands Developers can set the transaction isolation level for individual queries and saves. There is a TransactionSettings class and a TransactionSettings property on the QueryStrategy class. The TransactionSettings class provides the ability to dynamically set: The Transaction Isolation level of a Query (or Save) The Transaction Timeout to be applied to a Query (or Save) Note: The Default Transaction Isolation Level when you create the TransactionSettings object is ReadCommitted. This is not always the most performant choice, so be sure to research the isolation level appropriate for your application. Cache a query Last modified on March 23, 2011 22:52 Contents Query Cache Removing an entity clears the query cache Cache use when disconnected Modifications Stale entity data The DevForce EntityManager maintains two caches that you can use to cache a query. An entity cache that contains every entity that gets added to an EntityManager regardless of how those entities are retrieved or created. A query cache that contains every query that has been processed against a backend datastore that satisfies a specific set of criteria described later in this section. The rules by which a query's results are processed into the EntityManager cache in determined by a query strategy. When following the default, normal strategy, the EM tries first to satisfy a query from data in its cache; it reaches out to the data source only if it must.
Query Cache When a EntityManager begins to process a normal query, it checks its query cache to see if it has processed this exact query before. This query cache is available for programatic manipulation via the an EntityManager's QueryCache property. 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 entity cache without consulting the data source. A one-to-many entity navigation, such as from employee to the employees orders, is translated implicitly to an IEntityQuery instance 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, or add queries to the cache that he or she knows can be satisfied from the cache. DevForce caches queries to improve performance. This analysis applies to both entity queries and entity navigation. Both use Optimized fetch strategy by default. Consider a query for employees with FirstName = "Nancy". The QueryStrategy is Normal which means the fetch strategy is Optimized, which means that the retrieval will be from cache when possible. 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 Cache. The EntityManager stores the query in the query cache when (a) the query is successful, (b) it searched the data source (not just the cache), and c) the query is invertible. The second time it found the query in the Query Cache and thus knew it could apply the cache to the query instead. If we change "Nancy Davolio" 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, well 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 didnt go to the data source so it doesnt 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 FetchStrategy that looks at the database first. Clear the query cache explicitly by calling EntityManager.QueryCache Clear method. Clear the query cache implicitly by removing any entity from the entity cache. Removing an entity clears the query cache When we remove an entity from an EntityManagers entity cache ( via the RemoveEntity or RemoveEntities methods), DevForce automatically clears the entire query cache for the EntityManager. Thats right it erases the EntityManagers memory of all* the queries it has performed. Note that we are NOT talking about deleting an entity here. Deletion does not cause a clearing of the query cache. Suppose we frequently query for employees hired this year. If we issue this query twice, the first query fetches the employees from the database; the second retrieves them from the cache. The second query is almost instantaneous. Then we remove an unrelated entity such as a Customer or an Address. We query again. Instead of reading from the cache as it did before, the EntityManager goes back to the database for these employees. Seems unfair, doesnt it? But its the safe thing to do. If we issue the same query multiple times, we expect the same results every time. We expect a different result only if data relevant to our query have changed. The EntityManager will search the local cache instead of the database only if it "believes" that all essential information necessary to perform the query are resident in the cache. If it "thinks" that the cache has been compromised, it should go back to the data source to satisfy the query. Removing an entity compromises the cache. For sure it invalidates at least one query the query that fetched it in the first place. But is that the only invalidated query? The EntityManager does not know. So it does the safe thing and forgets all queries. You and I know (or we think we know) that removing a Customer or Address has no bearing on employees hired this year. The EntityManager is not so sure. There are circumstances when (a) we have to remove an entity and (b) we are certain that no queries will be adversely affected. For example, our query may return entities which weve marked as inactive. We never want inactive entities in our cache but, for reasons we need not explain here, we have inactive entities in the cache. We want to remove those entities. Being inactive they cannot possibly contribute to a correct query result. Unfortunately, removing those entities clears the entire query cache. The EntityManager will satisfy future queries from the database until it has rebuilt its query cache. This is not a problem if we rarely have to purge inactive entities. But what if we have to purge them after almost every query?. (This is not a rare scenario.) We will never have a query cache and we will always search the database. The performance of our application will degrade. Fortunately, there is 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(entitiesToRemove, false) The false parameter tells the EntityManager 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. Cache use when disconnected When the EntityManager is in the disconnected state, it will satisfy a navigation, or a query submitted with the Normal QueryStrategy, from the entity cache alone; it will not attempt to search the data source. The EntityManager raises an exception if it discovers during query processing that it cant reach the data source and a query strategy has been specified that requires that the data source be accessed. See Take offline to learn more about disconnected operations. 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 wont be updated until the application tells the EntityManager to save the changed object. It follows that the data source version can differ from our cached copy either because we modified the cached copy or because another user saved a different version to the data source after we retrieved our copy. It would be annoying at best if the EntityManager overwrote our local changes each time it queried the data source. Fortunately, in a normal query, the EntityManager will only replace an unmodified version of an object already in the cache; our modified objects are preserved until we save or undo them. Stale entity data All of this is convenient. But what if another user has made changes to a cached entity? The local application is referencing the cached version and is unaware of the revisions. For the remainder of the user session, the application will be using out-of-date data. The developer must choose how to cope with this possibility. Delayed recognition of non- local changes is often acceptable. A list of U.S. States or zip codes is unlikely to change during a user session. Employee name changes may be too infrequent and generally harmless to worry about. In such circumstances the default caching and query behavior is fine. If concurrency checking is enabled and the user tries to save a changed object to the data source, DevForce will detect the collision with the previously modified version in the data source. The update will fail and DevForce will report this failure to the application which can take steps to resolve it. Some objects are so volatile and critical that the application must be alert to external changes. The developer can implement alternative approaches to maintaining entity currency by invoking optional DevForce facilities for managing cached objects and forcing queries that go to the data source and merge the results back into the cache. The facilities for this are detailed in the Query Strategy section.
Secure the query with attributes The server should only honor a query coming from a client if the client is properly authorized. The EntityServerQueryInterceptor is the most flexible way to evaluate and authorize a query but it requires code. You may be able to satisfy your query security requirements declaritively by adding security attributes either to the entity type or to named query methods. This topic explains what those attributes are and how they work.
Security attributes Security attributes are the easiest way to authorize client queries. You can add attributes to entity class definitions and to named query methods. Here is a table summarizing query security attributes, followed by an explanation of how to add them, what they do, and when DevForce applies them. Attribute Summary ClientQueryPermissions Enable or disable the client's right to use certain query features such Include clauses and projections (Select clauses). RequiresAuthentication Whether client must be authenticated to execute this named query or refer to the adorned entity type in a query. RequiresRoles Which client roles are required in order to execute this named query or refer to the adorned entity type in a query. ClientCanQuery Whether the client can refer to this type in a query. This attribute can be applied to an entity type but not to a named query method. ClientQueryPermissions The ClientQueryPermissions attribute enables (or disables) certain query features. At the moment it controls two client query features: 1. Include clauses: an Include clause adds related entities to query results. 2. Projections: a "projection" is a query that uses the Select or SelectMany LINQ operation to change the type of the result. The attribute specifies a flag enumeration, ClientQueryPermissions, that captures the permission combinations. The enum values are: Minimal (no Includes, no projections) AllowIncludes AllowProjections All (allow both includes, and projections) You can apply this attribute to an entity type or to a named query method. Here is an example of the attribute applied to the Orders type. C# [ClientQueryPermissions(ClientQueryPermissions.AllowProjections)] public partial class Order{...} VB <ClientQueryPermissions(ClientQueryPermissions.AllowProjections)> Partial Public Class Order ... When both the entity type and a named query method have conflicting attributes, the named query attribute takes precedence. You can associate this attribute with specific user roles and you can apply the attribute several times in order to express different permissions for different roles. In the following GetGoldCustomers named query example, administrators can use Includes and Projections with this query but no one else can: C# [ClientQueryPermissions(ClientQueryPermissions.All, "admin")] [ClientQueryPermissions(ClientQueryPermissions.Minimal)] public IQueryable<Customer> GetGoldCustomers() {...} VB <ClientQueryPermissions(ClientQueryPermissions.All, "admin")> _ <ClientQueryPermissions(ClientQueryPermissions.Minimal)> _ Public Function GetGoldCustomers() As IQueryable(Of Customer) ... This attribute is beta as of version 6.1.0. A known issue causes DevForce to improperly apply this attribute to LINQ statements returned by a named query. RequiresAuthorization and RequiresRoles These two attributes serve the same purpose as the like-named WCF RIA Services attributes. RequiresAuthentication ensures that a user must be authenticated to query the entity or use the named query method. RequiresRoles ensures that only users with specific roles can query the entity or use the named query method. The EntityServerSaveInterceptor uses these same attributes to determine if the client is authorized to save the entity type.
ClientCanQuery The ClientCanQuery attribute is similar to the RequiresRoles attribute but there are differences. You can only apply the ClientCanQuery to entity types, not named queries. It offers a little more precision when specifying which kinds of users can reference an entity type in a query. Example Effect ClientCanQuery(false) No user can query with the type ClientCanQuery(true) All users can query with the type ClientCanQuery(AuthorizeRolesMode.Any, role1, role2 ...) A user with any of the mentioned roles can query with the type. ClientCanQuery(AuthorizeRolesMode.All, role1, role2 ...) Only a user with all of the mentioned roles can query with the type. ClientCanQuery is true by default so you generally do not need to add this attribute to your entity classes. However, you can reverse the default (see the discussion of DefaultAuthorization below) which would mean that, by default, no client could query with this type. If you change the default in this way, you may want to add this attribute to those types which should be client queryable and set it to true explicitly. The ClientCanQuery attribute is evaluated after RequiresAuthentication and RequiresRoles. Add attributes to entity types in the EDM Designer When you created your entity model, you may have noticed in the EDM Designer that each Entity has DevForce properties that govern the ability of the client to query and save:
The CanQuery property translates to the ClientCanQuery attribute on the generated entity class. The "Allow includes" and "Allow projections" properties combine to determine a ClientQueryPermissions attribute on the generated entity class. The property values are tri-state: True, False, and Default. "Default" means "rely on the EntityServerQueryInterceptor's default" as described below and DevForce won't generate the corresponding attribute. Because the "Allow ..." properties resolve to a single attribute; either both are "default" or neither is. Suppose we kept the default values for the two "Allow ..." properties and disabled (made false) the client's ability to query this type. The generated class would look like this: C# [IbEm.ClientCanQuery(false)] public partial class OrderDetail : IbEm.Entity {} VB <IbEm.ClientCanQuery(False)> Partial Public Class OrderDetail Inherits IbEm.Entity End Class Turning off all direct query access may seem a bit draconian, but this is a valid approach for those types that you really only want to allow on the server. Add attributes to the entity partial class You don't have to rely on the EDM designer to generate the attributes. You can add them to the entity's partial class where you can be more particular and assign permissions by role as seen in this example. C# [ClientCanQuery(AuthorizeRolesMode.Any, "Admin", "Sales")] public partial class Order : IbEm.Entity {} VB <ClientCanQuery(AuthorizeRolesMode.Any, "Admin", "Sales")> Partial Public Class Order Inherits IbEm.Entity End Class You'll need the partial class if you want to add the RequiresAuthentication or RequiresRoles attributes. C# [RequiresAuthentication] public partial class OrderDetail : IbEm.Entity {} VB <RequiresAuthentication> Partial Public Class OrderDetail Inherits IbEm.Entity End Class Add attributes to named query methods You can add any of the query security attributes to the definition of a named query method except ClientCanQuery. Here's and example restricting a specialized named query to adminstrators C# [RequiresRoles("admin")] public IQueryable<Customer> GetGoldCustomers() {...} VB <RequiresRoles("admin")> Public Function GetGoldCustomers() As IQueryable(Of Customer)... Attributes and the EntityServerQueryInterceptor The security attributes don't do anything on their own. They are metadata to be read and interpreted. Your code can read the metadata, perhaps to control the appearance and capabilities of the UI. The DevForce EntityServerQueryInterceptor reads and interprets the metadata when it processes the query. You can derive from this class and override virtual methods to introduce custom logic of your own including custom query security logic. EntityServerQueryInterceptor.AuthorizeQuery is the primary method to override. This method first calls GetClientQueryPermission to confirm that the client query is only using permitted features. Then it calls the ClientCanQuery method for each entity type involved in the original client query in order to verify that the client is authorized to refer to that type in a query. The default ClientCanQuery method discovers and applies the following attributes in sequence: RequiresAuthentication RequiresRoles ClientCanQuery It bears repeating that this phase of the analysis applies to the original client query, not the named query. Types specified inside the named query get a free pass. For example, you can prevent client queries from using the Customer type by setting ClientCanQuery to false. If a client submitted a query for Customers, the server would reject it. But it is OK for the client to invoke the GetGoldCustomers named query even though its implementation involves the Customer type; a named query is written for and executed on the server and is presumed to be secure. Types appearing in Include clauses are checked as well For example, suppose you blocked query of the OrderDetails type by adorning that class with ClientCanQuery(false). The client could not query for OrderDetails directly. The client would also be unable to acquire OrderDetail entities indirectly via a query for Orders that had an "OrderDetails" Include clause. Defaults in the absence of attributes The default GetClientQueryPermission method looks for a ClientQueryPermissions attribute for the query. If it can't find that attribute, it substitutes the DefaultClientQueryPermissions. The default is "All" meaning that all potential client query features are permitted. You can override this property to provide a more restrictive global setting such as "Minimal". Then the developer would have to enable query features explicitly on either the entity types or named query methods. The ClientCanQuery method looks for a ClientCanQuery attribute on each type involved in the client query. If it can't find that attribute, it allows or disallows the query based on the value of the DefaultAuthorization property. That value is true by default, meaning that a type without a ClientCanQuery attribute can be queried. You can reverse this default by overriding this property and returning false; then every type must carry the ClientCanQuery attribute or it can't be referenced in a client query. Summary of virtual authorization interception members The following Authorization-related properties and methods are all virtual which means that you can override them in your custom derived EntityServerQueryInterceptor. Member Summary AuthorizeQuery The primary query authorization method; it orchestrates authorization and calls the other authorization members. GetClientQueryPermissions Get the permissions that govern certain query features. DefaultClientQueryPermissions Get the ClientQueryPermissions enum to use when no attribute is specified. ClientCanQuery Get whether the client can refer to the specified type in this query. DefaultAuthorization Get whether the type can be referenced in a query if it lacks a ClientCanQuery attribute. You typically delegate to DevForce base class members, either before or after running your code ... but you don't have to do so. For example, within the interceptor you could ignore DevForce's interpretation of the attributes when you have better, perhaps contradicting, information available to you. EntityServerQueryInterceptor and named queries When the client submits a query that invokes a named query, DevForce authorizes the named query first. You cannot bypass the RequiresAuthentication and RequiresRoles attributes that adorn a named query method. If the named query fails attributed authorization, the query fails before reaching the EntityServerQueryInterceptor. If the named query survives these attribute-based security checks, DevForce combines the output of the named query method with the original client query instructions and passes the product of that combination to the EntityServerQueryInterceptor. The interceptor has access to the original client query and the original named query method as described here which means you can analyze the parts in your own custom interceptor. Query lifecycle Last modified on March 23, 2011 22:54 When DevForce executes a standard query, a sequence of steps is followed that involves code executing both on the client where the query was submitted as well as on the EntityServer where, in most cases, a backend datastore resides. This sequence, the query lifecycle, can be intercepted by the developer on both the client and the server, via a variety of mechanisms. A workflow The table below shows the lifecycle of a query assuming a QueryStrategy of Normal. Table 1. Entity Query and Navigation Workflow When QueryStrategy = Normal Component Action Client Tier Application Code 1) The client application requests a particular set of entities (the desired entities) either by entity query or by entity navigation Client Tier 2) The EntityManager raises a Querying event. Listeners can see the query EntityManager and, optionally, cancel the query. 3) The EntityManager checks the QueryCache (and sometimes the EntityCache depending on the kind of query) to see if it can satisfy the query with the entities in the client-side cache. If so, it runs the query against the local cache only and jumps to step 14 below. 4) The EntityManager raises a Fetching event. Listeners can see the query and, optionally, cancel the query. 5) The EntityManager sends the query along with authentication information to the EntityServer on the middle tier. It may modify the request before sending it to the EntityServer if it can determine that some of desired entities are already in the client side cache. Middle Tier EntityServer 6) The EntityServer authenticates the client (the currently logged in "user") and creates a new instance of either a custom or a default EntityServerQueryInterceptor. The EntityServerQueryInterceptor is executed and it can either modify the query and/or run any developer- specified security checks in the AuthorizeQuery handler. If security checks fail, it raises a security exception and sends this back to the client tier. 7) Having passed security checks, the EntityServerQueryInterceptor calls its base ExecuteQuery method where the EntityServer converts the, possibly modified, query into a format applicable to the datasource being queried. This will often be an Entity Framework LINQ to Entities query. Data source Data Source 8) The data source performs the query or queries and returns one or more result sets back to the EntityServer. Middle Tier EntityServer 9) The Entity Framework converts the result sets returned from the data source into ADO.NET entities and delivers them to the EntityServerQueryInterceptor ( from above) and performs any post query authorization via the AuthorizeQueryResult method. 10) The EntityServer repackages the result set into a format that can be transmitted efficiently. It then ships the entity data back to the client side EntityManager. 11) After transmission, the EntityServer allows the servers local copy of the entities to go out of scope and the garbage collector reclaims them. This enables the EntityServer to stay stateless. Client Tier EntityManager 12) Compares fetched entities to entities already in the cache. Adds new entities to the cache. Replaces matching cached entities that are unmodified (in essence refreshing them). Preserves cached entities with pending modifications because the query strategy is normal. 13) Reapplies the original query to the cache to locate all desired entities. 14) Raises the Queried event. Listeners can examine the list of entities actually retrieved from the data source. 15) Returns the desired entities to the application. Client Tier Application Code 16) The entities are available for processing. Client-side life cycle Last modified on March 23, 2011 22:56 Client-side query life cycle events on the EntityManager include Querying, Fetching, and Queried. These are summarized in the table below. DevForce raises the client-side Querying event as query execution begins. The Fetching event is raised before sending the query to the EntityServer; if a query can be satisfied from cache, then Fetching is not raised. The Queried event is raised just before returning query results. We can listen to these events by attaching a custom handler. Both the Querying and the Fetching events provide the query object. Your handler can examine the object (it implements IEntityQuery and choose to let the query continue as is, continue after modification, or cancel it. If you cancel the query, the Entity Manager method returns as if it found nothing. If the method returns a scalar entity, it yields the return entity types Null Entity; otherwise, it returns a null entity list. Beware of canceling an entity navigation list query method. The Queried event fires just before the query method returns. Entities have been fetched and merged into the cache. The event arguments include a list, EntitiesChanged, of any entities added to or modified in the cache. Note that this list could include entities of the target entity type the kind we expected returned from the query but also of other types brought in via Includes or by virtue of query inversion. Client-Side Life-Cycle Events Event EventArgs Comments Typical Uses of the Corresponding Event Handler Querying EntityQueryingEventArgs Raised as query execution begins. Provides access to the submitted query. Modify the query being submitted, or refuse the request for data. Fetching EntityFetchingEventArgs Raised before sending the query to the EntityServer. Provides access to the submitted query. If a query can be satisfied from cache, Fetching is not raised. Modify the query being submitted, or refuse the request for data. Queried EntityQueriedEventArgs Raised just before returning query results. Provides access to the complete result set, as well as the subset representing changes to the cache. Modify the objects that were returned by the query Corresponding to each of these events is a protected virtual method on the EntityManager that can be used to perform the same task directly in a subclassed EntityManager. Member Summary OnQuerying The virtual method that a derived EntityManager can override to raise the Querying event. OnFetching The virtual method that a derived EntityManager can override to raise the Fetching event. OnQueried The virtual method that a derived EntityManager can override to raise the Queried event. EntityQueryingEventArgs Property PropertyType Access Description Query IEntityQuery get, set The query about to be executed. The query can be modified here, but the final shape must remain the same. Cancel bool get, set Allows the query to be canceled. EntityFetchingEventArgs Property PropertyType Access Description Query IEntityQuery get The query about to be executed. The query can no longer be modified here, use the Querying event if you need modification. Cancel bool get, set Allows the query to be canceled. EntityQueriedEventArgs Note that this is the non-generic version of the EntityQueriedEventArgs<T> class. As a result the Query and Results properties here are non-generic as well. i.e. IEntityQuery vs IEntityQuery<T>, IEnumerable vs IEnumerable<T>. Property PropertyType Access Description Query IEntityQuery get The query just executed. Results IEnumerable get The results of the query. ChangedEntities IList<Object> get The list of every entity that was either added or modified in the EntityManager's cache as a result of this query. The ChangedEntities list may differ from the Results property since the Results will include only those entities directly queried, and not entities fetched due to query inversion or use of an Include. WasFetched bool get Whether the operation actually required a trip to the database. Many queries can be completed without having to go to the database. ResolvedFetchStrategy FetchStrategy get The FetchStrategy actually used to process the query. This is really only useful when an 'optimized' FetchStrategy was stipulated when executing the query. What is returned here is the query strategy that 'optimized' determined was most appropriate. Server-side life cycle Queries processed by the EntityServer pass through an instance of the DevForce EntityServerQueryInterceptor. You can alter the course of that processing by creating a 'custom' subclass of the EntityServerQueryI nterceptor and overriding its virtual properties and template methods. DevForce can discover the existence of your class automatically.
Interceptor design guidelines You don't have to write a custom interceptor class. The EntityServer will use the DevForce EntityServerQueryInterceptor if it doesn't find a custom subclass. Many production applications do include a custom interceptor. The EntityServerQueryInterceptor class resides in the IdeaBlade.EntityModel.Server assembly which must be referenced by the project that contains your custom subclass. You can write only one custom interceptor. It should be a public class and must have a public default, parameterless constructor (if it has a constructor). Please keep your named custom interceptor stateless if possible. DevForce creates a new instance of this class for each query performed by the server so you generally don't have to worry about threading issues with instance state. If you decide to maintain static state, give great care to ensuring safe concurrent access to that state. Avoid putting anything in the interceptor other than what is strictly necessary to achieve its purpose. The interceptor is a poor choice for a grab-bag of server-side features. You don't have to override any of the template methods; the default base implementations all work fine. You may wish to be explicit in your custom class and override every template method; your override can simply delegate to the base implementation. Make sure that the assembly containing your custom interceptor is deployed to the server such that it can be discovered. Assembly discovery is discussed here. Interceptor template methods Most of the template methods provided by the EntityServerQueryInterceptor base class have no parameters because all of the relevant data are provided by properties and methods on each instance of the class. This also allows IdeaBlade to extend these base classes in the future without breaking custom developer code. Many of the template methods described below return a boolean result with a base implementation that returns true. A return value of false indicates that the operation should be cancelled. Query results include a flag to indicate a cancelled operation. Note that the base implementation of the authorization method does not return false. It treats an unauthorized query as an exception, not a cancellation. Method Typical Uses AuthorizeQuery Determine if the user is authorized to make the request. FilterQuery Modify the query as needed. ExecuteQuery Can intercept immediately before and after the query is executed. Always call the base method to allow DevForce to execute the query. AuthorizeQueryResult Allows for authorization of types in the result set. In addition to the above, there are supporting properties or methods that can be overridden. For data retrieval, these include DefaultAuthorization ClientCanQuery, and ShouldAuthorizeQueryResult Authorization During data retrieval operations, the first interceptor called is AuthorizeQueryResult(). The default implementation checks the ClientCanQuery property, passing it the target type of the query. ClientCanQuery in turn checks the DefaultAuthorization property. If you select an Entity in the Entity Data Model Designer, you will see associated DevForce Code Generation properties named Can Query and Can Save. By default, these have their values set to the value Default; other options are True and False.
If you leave the default values unchanged and you also leave unchanged the value of DefaultAuthorization property of your EntityServerQueryInterceptor class, then all entity types will be allowed in queries and saves. You can however override the settings in the EDMX file with code in your EntityServerQueryInterceptor. C# /// <summary> /// /// </summary> /// <remarks> /// Override this property to change the Default authorization result (whether /// authorization succeeds if no Authorization attributes are found). /// /// The default is true - allowing any entity type not specifically marked /// with an authorization attribute to be queried. /// </remarks> protected override bool DefaultAuthorization { // Always require explicit authorization attributes on entities. get { return false; } } VB ''' <summary> ''' ''' </summary> ''' <remarks> ''' Override this property to change the Default authorization result (whether ''' authorization succeeds if no Authorization attributes are found). ''' ''' The default is true - allowing any entity type not specifically marked ''' with an authorization attribute to be queried. ''' </remarks> Protected Overrides ReadOnly Property DefaultAuthorization() As Boolean ' Always require explicit authorization attributes on entities. Get Return False End Get End Property You have just ordered that entities types are not authorized for queries, by default. An attempt to execute a query such as the following C# var customersQuery = _em1.Customers.ToList(); VB Dim customersQuery = _em1.Customers.ToList() now results in an exception, thrown in response to the call to base.AuthorizeQuery():
If, however, you return to the EDM Designer and set the Can Query property on the Customer entity to True, then you will again be able to submit queries against the Customer type. Suppose, having set DefaultAuthorization to false, you were to authorize the Order type (along with the Customer type), and then submit the following query: C# var ordersQuery = _em1.Orders .Include("Customer") .Include("OrderDetails").ToList(); VB Dim ordersQuery = _em1.Orders _ .Include("Customer") _ .Include("OrderDetails").ToList() The query is requesting Orders and Customers, both of which you have explicitly designated as queryable; but also OrderDetails, which you have not made queryable. Is the query executed? If fact it is, as the ClientCanQuery test only looks at the target QueryableType of the query. To enforce a proscription on the retrieval of OrderDetails by way of Includes, you will need to implement logic to screen OrderDetails in an override of the AuthorizeQueryResult() interceptor, and also override the ShouldAuthorizeQueryResult property, setting its value to true: C# protected override bool AuthorizeQueryResult() { int countOrderDetails = this.QueriedEntities .Where(e => e.GetType() == typeof(OrderDetail)).Count(); if (countOrderDetails > 0) { return false; } return true; }
protected override bool ShouldAuthorizeQueryResult { get { return true; } } VB Protected Overrides Function AuthorizeQueryResult() As Boolean Dim countOrderDetails As Integer = Me.QueriedEntities.Where _ (Function(e) e.GetType() Is GetType(OrderDetail)).Count() If countOrderDetails > 0 Then Return False End If Return True End Function
Protected Overrides ReadOnly Property ShouldAuthorizeQueryResult() As Boolean Get Return True End Get End Property Such logic would result in an exception upon the attempt to submit the query: Filtering the query If the query is authorized, execution proceeds and the FilterQuery interceptor is called. Here you may add filters conditions to the query such as these: C# /// <summary> /// /// </summary> /// <returns></returns> /// <remarks> /// May be overridden to perform any logic before the query is submitted. /// This can include adding to the QueryFilters collection or by modifying /// the query directly using the Query.Filter method or the /// QueryFilterCollection classes. /// </remarks> protected override bool FilterQuery() { // Restrict Customer and Employee results to those based in the UK QueryFilters.AddFilter<Customer>(q => q.Where(c => c.Country == "UK")); QueryFilters.AddFilter<Employee>(q => q.Where(e => e.Country == "UK")); return true; } VB ''' <summary> ''' ''' </summary> ''' <returns></returns> ''' <remarks> ''' May be overridden to perform any logic before the query is submitted. ''' This can include adding to the QueryFilters collection or by modifying ''' the query directly using the Query.Filter method or the ''' QueryFilterCollection classes. ''' </remarks> Protected Overrides Function FilterQuery() As Boolean ' Restrict Customer and Employee results to those based in the UK QueryFilters.AddFilter(Of Customer)(Function(q) q.Where _ (Function(c) c.Country = "UK")) QueryFilters.AddFilter(Of Employee)(Function(q) q.Where _ (Function(e) e.Country = "UK")) Return True End Function Executing the query Next, the ExecuteQuery() interceptor is called. That should include a call to base.ExecuteQuery(), which will actually execute the submitted query. You can insert logic both before and after the call to base.ExecuteQuery(). Virtual methods in more detail Virtual methods listed in the order that they will be called - protected virtual bool AuthorizeQuery() Determines whether the query can be executed. The base implementation call performs an analysis of the query expression tree by calling the ClientCanQuery method defined below for each entity type used in the query to determine if any unauthorized types are being accessed. Note that this will not detect the case where a polymorphic query is executed against a base type and one of its subtypes has a ClientCanQuery restriction. In this case, the ShouldAuthorizeQueryResult property should be set to true and the AuthorizeQueryResult method may be implemented. It is not usually necessary to override the AuthorizeQueryResult method; simply returning true from ShouldAuthorizeQueryResult is usually sufficient. protected virtual bool FilterQuery() Provides a point at which the Query property can be modified before the query is executed. Optionally, the QueryFilters property can be utilized to add filters that will be applied when the query is actually executed, without actually modifying the Query property itself. protected virtual bool ExecuteQuery() Performs the actual execution of the query. The query itself may be modified before calling the base implementation of this method and logging or other post processing operations may be performed after the base implementation is called. Note that the base implementation must be called in order for the query to be executed. It is during the call to the base implementation that any QueryFilters defined earlier will be applied. protected virtual bool AuthorizeQueryResult() This method is only called if the ShouldAuthorizedQueryResult property returns true. This methods base implementation actually walks through all of the entity types to be returned and throws an exception if the ClientCanQuery method below returns false for any of the specified types. The QueriedEntities collection mentioned below is also available and may be used to examine every entity being returned. Other virtual properties and methods - protected virtual bool DefaultAuthorization { get; } This property defines the default authorization behavior for any types that do not have a ClientCanQuery attribute defined. The base implementation returns true which means that by default any type not otherwise restricted is queryable. By returning a false here, any types that are not specifically marked as queryable will restricted. protected virtual bool ClientCanQuery(Type type) This method is called from the base implementation of both the AuthorizeQuery as well as the AuthorizeQueryResult methods. It may be overridden to add additional restrictions or to relax existing ones. If adding restrictions, make sure that the base implementation is called. Helper properties protected IEntityQuery Query { get; set; } The current query. This can be modified anytime before query execution. protected EntityQueryFilterCollection QueryFilters { get; } A collection of Query filters that will automatically applied to every query. Use the FilterQuery method to add or remove filters from this collection. protected bool IsServerQuery { get; } Returns true if the 'current query' was issued on the server. This can occur as a result of a query made from within this interceptor using the EntityManager mentioned below or as a result of a query invoked by a server implementation of an InvokeServerMethod call. protected EntityManager EntityManager { get; } An untyped EntityManager that can be used to query for additional information. Note that this is not the original client-side EntityManager on which the query request was made. It is a 'preauthenticated' server-side EntityManager, and its cache is empty. Because of its "preauthenticated" nature, there is no Principal attached to this entity manager and any operations that it performs bypass regular authentication. Because this EntityManager can only be accessed on the server from within code deployed to the server all operations that it performs are considered "priviledged". Because this EntityManager is untyped, any entity type may queried with it. Queries against this EntityManager are usually composed via a technique shown in the following example, ( the code below is assumed to be executing within the context of some EntityManagerQueryInterceptor method). Note the use of the EntityQuery<T> constructor. C# var newQuery = new EntityQuery<Customer>().Where(c => c.CompanyName.StartsWith("S")) var custs = newQuery.With(EntityManager).ToList(); protected IList<Object> QueriedEntities { get; } This property is only available after the query has executed and returns a list of every entity that was queried, whether directly or thru an 'Include' or 'Query Inversion'. Any modifications to this collection will be ignored. protected IPrincipal Principal { get; } The IPrincipal from the user session requesting this operation. BETA Features The following features may or may not be retained in the product in the future, and please contact support with any feedback that you have regarding them. protected void ForceResult(Object result, NavigationSet navSet = null, QueryInfo queryInfo = null) May be called to force the results of the query. The shape of the result being foreced must match that of the result of the query being executed. This method can be called either before or after the execution of the query.
protected bool ResultsForced { get; } Returns true if the ForceResult call above was made. Intercepting and modifying queries during execution There will be points where the need arises to intercept and modify a query while it is executing. DevForce provides an extension method, Filter(), that can be used to superimpose one or more independently defined filter conditions upon an existing query. Filter extension method Filter() differs from Where() in two ways 1) Filters can be safely applied to queries that do not 'fit' the type of the query being processed. In these cases the Filter is simply ignored. 2) Filters operate on any subselects within the query in addition to result type of the query. Where clause's operate in a chained fashion on whatever result type immediately precedes them. Filter()s primary motivating use case is the need to apply query interception, either client or server-side, to submitted queries; although it is perfectly possible to use it in other contexts. In the case of query interception, there is a need to work with 'untyped' queries, or queries where both the return type as well as the source type of a query is very likely unknown at runtime, so there is a need to be able to apply a possibly large number of 'Filter's to any query that comes though and only have the 'applicable' filters apply to each query. Additionally, there is often a need to modify 'intermediate' portions of the query in addition to just restricting its final result. For example, suppose your applications 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 an EntityServerQueryInterceptor 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 several overloads of Filter(), one that takes a Func<T> and another that takes an EntityQueryFilterCollection (each of whose members is a Func<T>). Lets look at some examples: C# var query = _em1.Territories.Where(t => t.TerritoryID > 100); var newQuery = query.Filter((IQueryable<Territory> q) => q.Where(t => t.TerritoryDescription.StartsWith("M"))); VB Dim query = _em1.Territories.Where(Function(t) t.TerritoryID > 100) Dim newQuery = query.Filter(Function(q As IQueryable(Of Territory)) _ q.Where(Function(t) t.TerritoryDescription.StartsWith("M"))) In this example we have used the overload of Filter which takes as its argument a Func delegate. Said delegate takes an IQueryable<T> -- essentially a list of items of type T and returns an IQueryable<T>. The IQueryable<T> that goes in is the one defined by the variable query, defined as C# _em1.Territories.Where(t => t.TerritoryID > 100) VB _em1.Territories.Where(Function(t) t.TerritoryID > 100) The one that comes out is the one that went in minus those Territories whose Description property value begins with the letter M. In the first example, above, our filter applies to the querys root type, Territory. We arent limited to that: we can also apply filters to other types used in the query. Consider the following: C# var q1 = _em1.Customers.SelectMany(c => c.Orders .Where(o => o.ShipCity.StartsWith("N"))); var q1a = q1.Filter((IQueryable<Order> q) => q.Where(o => o.FreightCost > maxFreight)); VB Dim q1 = _em1.Customers.SelectMany(Function(c) c.Orders. _ Where(Function(o) o.ShipCity.StartsWith("N"))) Dim q1a = q1.Filter(Function(q As IQueryable(Of Order)) q. _ Where(Function(o) o.FreightCost > MaxFreight)) The root type for this query is Customer, but the query projects OrderSummaries as its output, and it is against OrderSummaries that we apply our filter. 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. Lets look at another example of filtering one some type other than the querys root type: C# var q1 = _em1.Customers.Where(c => c.Orders.Any (o => o.ShipCity.StartsWith("N"))); var q1a = q1.Filter((IQueryable<Order> q) => q.Where(o => o.FreightCost > maxFreight)); {{/code}} VB Dim q1 = _em1.Customers.Where(Function(c) c.Orders.Any _ (Function(o) o.ShipCity.StartsWith("N"))) Dim q1a = q1.Filter(Function(q As IQueryable(Of Order)) q. _ Where(Function(o) o.FreightCost > MaxFreight)) In the absence of the filter, the above query would retrieve Customer objects: specifically, Customers having at least one Order whose ShipCity begins with the letter "N". The filter potentially reduces the set of Customers retrieved by imposing an additional condition on their related OrderSummaries (again, on the value of their Freight property). Now lets look at a use of Filter() involving conditions on more than a single type. C# var eqFilters = new EntityQueryFilterCollection(); eqFilters.AddFilter((IQueryable<Customer> q) => q.Where(c => c.Country.StartsWith("U"))); eqFilters.AddFilter((IQueryable<Order> q) => q.Where(o => o.OrderDate < new DateTime(2009, 1, 1))); var q0 = _em1.Customers.Where(c => c.Orders.Any (o => o.ShipCity.StartsWith("N"))); var q1 = q0.Filter(eqFilters); VB Dim eqFilters = New EntityQueryFilterCollection() eqFilters.AddFilter(Function(q As IQueryable(Of Customer)) q. _ Where(Function(c) c.Country.StartsWith("U"))) eqFilters.AddFilter(Function(q As IQueryable(Of Order)) q. _ Where(Function(o) o.OrderDate < New Date(2009, 1, 1))) Dim q0 = _em1.Customers.Where(Function(c) c.Orders.Any( _ Function(o) o.ShipCity.StartsWith("N"))) Dim q1 = q0.Filter(eqFilters) In the above snippet, we instantiate a new EntityQueryFilterCollection, to which we then add two individual filters, each of which is a Func<T>. The first filter added imposes a condition on the Customer type; the second imposes a condition on the OrderSummary type. Note that we could now apply these filters to any query whatsoever. If the targeted 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 targeted 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.Orders .Where(o => o.OrderDate < new DateTime(2009, 1, 1)) .Any(o => o.ShipCity.StartsWith("N"))); VB Dim q0 = _em1.Customers. _ Where(Function(c) c.Country.StartsWith("U")). _ Where(Function(c) c.Orders. _ Where(Function(o) o.OrderDate < New Date(2009, 1, 1)). _ Any(Function(o) o.ShipCity.StartsWith("N")))
Build queries dynamically Last modified on October 27, 2011 13:09 Contents The problem The ESQL option LINQ expressions are strongly typed and static. They are easy to compose when you know the type of thing you want to query at compile time. But they are difficult to create if you don't know the type until the application runs. Many applications let the user specify the entity type to retrieve, the criteria to filter with, the properties to sort and group by. DevForce dynamic query building components help you build and execute LINQ queries on the fly based on information supplied at runtime.
The problem "LINQ" stands for "Language Integrated Query". LINQ is designed to be written in the same manner as other code, rubbing shoulders with procedural statements in the body of your application source files. C# and Visual Basic are statically typed languages so the main pathway for writing LINQ queries is to compose them statically as strongly typed query objects that implement the IQueryable<T> interface. Standard LINQ queries, and almost of the LINQ examples in the DevForce Resource Center, are static and strongly typed. Here is a typical example: C# IQueryable<Customer> customers = _myEntityManager.Customers; var query = customers.Where(c => c.CompanyName = "Acme"); VB Dim customers As IQueryable(Of Customer) = _myEntityManager.Customers Dim query = customers.Where(Function(c) c.CompanyName = "Acme") The _myEntityManager.Customers expression returns an instance of a query object that implements IQueryable<Customer>. The strongly typed nature of the expression is what allows IntelliSense and the compiler to interpret the remainder of the statement. What if 'T' (Customer in this case) is not known until runtime; from a compiler perspective we no longer have an IQueryable<T> but have instead just an IQueryable. And IQueryable, unfortunately, does not offer any of the extension methods that we think of a standard LINQ; in other words, no Where, Select, GroupBy, OrderBy, Any, Count etc, methods. How do we write a query when we don't have the type available at runtime? C# var typeToQuery = typeof(Customer); IQueryable<???> someCollection = << HOW DO I CREATE AN IQUERYABLE OF "typeToQuery"?>> ; var query = somecollection.Where( << HOW DO I COMPOSE A LAMBDA EXPRESSION WITHOUT A COMPILE TIME TYPE >> ); VB Dim typeToQuery = GetType(Customer) IQueryable(Of ???) someCollection = << HOW DO I CREATE AN IQUERYABLE OF "typeToQuery"?>> Dim query = somecollection.Where(<< HOW DO I COMPOSE A LAMBDA EXPRESSION WITHOUT A COMPILE TIME TYPE >>) A second issue can occur even if we know the type of the query but we have a number of 'where conditions' or predicates, that need to be combined. For example C# Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A"); Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.StartsWith("B"); VB Dim expr1 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("A") Dim expr2 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("B") It turns out that we can use either of these expressions independently, but it isn't obvious how to combine them. C# var query1 = myEntityManager.Customers.Where(expr1); // ok var query2 = myEntityManager.Customers.Where(expr1); // ok var queryBoth = myEntityManager.Customers.Where(expr1 && expr2) // BAD - won't compile. VB Dim query1 = myEntityManager.Customers.Where(expr1) ' ok Dim query2 = myEntityManager.Customers.Where(expr1) ' ok Dim queryBoth = myEntityManager.Customers.Where(expr1 AndAlso expr2) ' BAD - won't compile. The ESQL option You can construct queries on the fly using DevForce "pass-thru" Entity SQL (ESQL). ESQL queries are strings that look like standard SQL, differing primarily (and most obviously) in their references to entity types and properties rather than tables and columns. You can build ESQL queries by concatenating strings that incorporate the runtime query critieria you gleaned from user input. Most developers prefer to construct LINQ queries with DevForce for two reasons: 1. Building syntactically correct ESQL strings can be more difficult to do correctly than writing DevForce dynamic queries. The dynamic query approach affords a greater degree of compiler syntax checking and the structure of the query is more apparent. You often know most of the query at design time; when only some of the query is variable you can mix the statically typed LINQ statements with the dynamic clauses ... as described in the topic detail. 2. ESQL queries must be sent to and processed on the server. You cannot apply ESQL queries to the local entity cache. LINQ queries, on the other hand, can be applied to the database, to the cache, or to both. This is as true of dynamic LINQ queries as it is of static queries. For many developers this is reason enough to prefer LINQ to ESQL.
Create a completely dynamic query Last modified on October 27, 2011 15:30 Contents EntityQuery.Create LINQ methods where no IQueryable<T> exists Most of the queries created using DevForce will be instances of some query subclass that implements IQueryable<T>. However, there will be cases where we will not be able to determine "T" until runtime. A "completely dynamic" query is one where we do not know the type "T" of the query at compile time. Because these queries cannot implement IQueryable<T> they will instead implement the IQueryable interface.
EntityQuery.Create The static EntityQuery.Create is a non-generic method that creates a new query for a specified type. Here is a function that employs EntityQuery.Create to produce a query that retrieves every instance of a type: C# public EntityQuery GetAll(Type entityType, EntityManager manager) { return EntityQuery.Create(entityType, manager); } VB Public Function GetAll(ByVal entityType As Type, ByVal manager as EntityManager) As EntityQuery Return EntityQuery.Create(entityType, manager) End Function It's a contrived example but you can imagine something like it supporting an application feature that lets users pick the type of entity to retrieve from a list. We use it to retrieve Products and compare this approach with the strongly typed LINQ statement that does the same thing ... when you know that you are always getting Products: C# var queryType = typeof(Product); // the type picked by the user EntityQuery query1 = GetAll(queryType, anEntityManager); EntityQuery<Product> query2 = anEntityManager.Products; VB Dim queryType = GetType(Product) ' the type picked by the user Dim query1 As EntityQuery = GetAll(queryType, anEntityManager) Dim query2 As EntityQuery(Of Product) = anEntityManager.Products These two queries are identical from the standpoint of how they get executed and what they return. The critical difference is that the compiler can't know the type of the query returned in the first case and therefore must return an EntityQuery (which implements IQueryable and ITypedQuery) instead of EntityQuery<T> (which implements IQueryable<T>). This has three ramifications: Completely dynamic queries typically derive from EntityQuery which implements IQueryable and ITypedQuery. These queries must be executed either with the IEntityQuery.Execute extension method or the EntityManager.Execute method; you can't use ToList(). Such queries return IEnumerable results instead of IEnumerable<T>. The following example illustrates these points: C# // IQueryable implementation var query1 = EntityQuery.Create(typeof(Product), anEntityManager); // can't call query1.ToList() because query1 is not an IQueryable<T> IEnumerable results = query1.Execute(); // Have to cast to work with Products IEnumerable<Product> products1 = results.Cast<Product>();
// IQueryable<T> implementation var query2 = anEntityManager.Products; IEnumerable<Product> products2 = query2.ToList(); VB ' IQueryable implementation Dim query1 = EntityQuery.Create(GetType(Product), anEntityManager) ' can't call query1.ToList() because query1 is not an IQueryable<T> Dim results As IEnumerable = query1.Execute() ' Have to cast to work with Products Dim products1 As IEnumerable(Of Product) = results.Cast(Of Product)()
' IQueryable<T> implementation Dim query2 = anEntityManager.Products Dim products2 As IEnumerable(Of Product) = query2.ToList() LINQ methods where no IQueryable<T> exists
LINQ queries, as defined in standard .NET depend on the IQueryable<T> and IEnumerable<T> interfaces. Because a "completely dynamic" query only implements the IQueryable interface there is no way to implement the "standard" LINQ operators. However, DevForce does provide a substitute. DevForce implements a separate set of extension methods that have the same names as the standard LINQ operators but operate on instances of objects that only implement IQueryable instead of IQueryable<T>. ( See IdeaBlade.Linq.QueryableExtensions). That covers many of the familiar LINQ operators and immediate execution methods. Some extension methods require further refinment to operate specifically on instances of DevForce EntityQuery (as opposed to EntityQuery<T>. These methods may be found in IdeaBlade.EntityModel.EntityQueryExtensions along side the other EntityQuery extension methods. They are distinguishable by their first parameter which is of type ITypedEntityQuery. The ITypedEntityQuery interface describes an EntityQuery that is actually an EntityQuery<T> but the type "T" will not be known until runtime and is therefore not available at compile time. This is not something that a developer will usually ever have to concern himself/herself with. The EntityQueryExtensions include the following methods that return an ITypedEntityQuery. EntityQueryExtensions.Where EntityQueryExtensions.OrderBySelector EntityQueryExtensions.Select EntityQueryExtensions.SelectMany EntityQueryExtensions.GroupBy EntityQueryExtensions.Take EntityQueryExtensions.Skip EntityQueryExtensions.Cast EntityQueryExtensions.OfType as well as the following immediate execution methods that take an ITypedEntityQuery and return a scalar result. EntityQueryExtensions.All EntityQueryExtensions.Any EntityQueryExtensions.Contains EntityQueryExtensions.Count An overload of EntityQueryExtensions.AsScalarAsync also extends ITypedEntityQuery so you can call the asynchronous scalar functions {First..., Count, etc.). These overloads differ from the standard LINQ overloads in that none of them involve parameters that are Expression<Func<T, ...>. This is again a ramification of the fact that the generic type "T" is not available at compile time for these methods. These methods instead take either a PredicateDescription, SortSelector, or ProjectionSelector in place of the strongly typed Expression<...>
Create dynamic "Where" clauses Last modified on October 27, 2011 15:30 Contents Flavors of dynamic where clauses Whats a predicate? Create a Where clause with two predicates Completely dynamic query The need for the ability to create a dynamic where clause occurs fairly frequently in applications that need to filter data based on a users input. In these cases you don't really know ahead of time what properties a user is going to want to query or what the conditions of the query are likely to be. In fact, you may not even know the type that you will be querying for until runtime. So you will need to be able to compose the query based on the inputs determined at runtime.
Flavors of dynamic where clauses
Ideally, when building a query we want to specify as little dynamically as we have to. The less dynamic a query is the better the compile time checking and intellisense will be for the query. Everything depends upon what we know and when we know it. Do we know the type of the query at compile time but need to wait until runtime to determine the properties to be queried and the conditions regarding these properties? Or do we not even know the type of the query until runtime? Whatever the case, the IdeaBlade.Linq.PredicateBuilder and the IdeaBlade.Linq.PredicateDescription classes are the tools used to deal with compile time uncertainty. As we review the PredicateBuilder API, you will see both typed and untyped overloads for most methods in order to support both compile and runtime scenarios. Whats a predicate? A LINQ where clause is intimately tied to the concept of a "predicate". So what is a predicate and how does it relate to a LINQ where clause. A predicate is a function that evaluates an expression and returns true or false. The code fragment... C# p.ProductName.Contains("Sir") VB p.ProductName.Contains("Sir") ...is a predicate that examines a product and returns true if the products ProductName contains the Sir string. The CLR type of the predicate in our example is: C# Func<Product, bool> VB Func(Of Product, Boolean) Which we can generalize to: C# Func<T, bool> VB Func(Of T, Boolean) This is almost what we need in order to build a LINQ where clause. The LINQ Where extension method is defined as follows: C# public static IQueryable<T> Where<TSource>( this IQueryable<T> source1, Expression<Func<T,bool>> predicate) VB public static IQueryable(Of T) Where(Of TSource) _ (Me IQueryable(Of T) source1, Expression(Of Func(Of T,Boolean)) predicate) Note that the "predicate" parameter above is C# Expression<Func<T, bool>> VB Expression(Of Func(Of T, Boolean)) When we refer to the idea that a "predicate" is needed in order to build a LINQ where clause, what we really mean is that we need to build a "predicate expression", or a Expression that is resolvable to a simple predicate. Create a Where clause with two predicates The snippet below comprises two statements, each of which uses PredicateBuilder.Make to create a PredicateDescription representing a single predicate (filter criteria). C# PredicateDescription p1 = PredicateBuilder.Make(typeof(Product), "UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24); PredicateDescription p2 = PredicateBuilder.Make(typeof(Product), "Discontinued", FilterOperator.IsEqualTo, true); VB Dim p1 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "UnitPrice", _ FilterOperator.IsGreaterThanOrEqualTo, 24) Dim p2 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "Discontinued", _ FilterOperator.IsEqualTo, True) A new query can then be composed and executed by an EntityManager as usual: C# var query = anEntityManager.Products.Where(p1.And(p2)) // The above query is the same as: //var queryb = anEntityManager.Products.Where(p => p.UnitPrice > 24 && p.Discontinued); VB Dim query = anEntityManager.Products.Where(p1.And(p2)) ' The above query is the same as: 'var queryb = anEntityManager.Products.Where(p => p.UnitPrice > 24 && p.Discontinued); We could have combined the individual PredicateDescriptions into another PredicateDescription variable: C# CompositePredicateDescription p3 = p1.And(p2); var query = anEntityManager.Products.Where(p3); VB Dim p3 As CompositePredicateDescription = p1.And(p2) Dim query = anEntityManager.Products.Where(p5) Learn more about combining predicates with PredicateBuilder and PredicateDescription classes. Completely dynamic query The Where clause in the previous examples were still applied to a strongly typed (non- dynamic) IQueryable<Product> implementation. To be more precise, the query root was of type IEntityQuery<Product>. What if we didn't know we were querying for Product at compile type? We'd know the type at runtime but we didn't know it as we wrote the code. We could use the EntityQuery.CreateQuery factory method and pass it the runtime type. CreateQuery returns a nominally-untyped EntityQuery object. C# var queryType = typeof(Product); // imagine this was passed in at runtime. var baseQuery = EntityQuery.CreateQuery(queryType , anEntityManager); var query = baseQuery.Where(p1.And(p2)); VB Dim queryType = GetType(Product) ' imagine this was passed in at runtime. Dim baseQuery = EntityQuery.CreateQuery(queryType , anEntityManager) Dim query = baseQuery.Where(p1.And(p2)) The EntityQuery result of CreateQuery implements ITypedEntityQuery. The Where clause in this example made use of the ITypedEntityQuery extension method instead of the IQueryable<T> extension method: C# public static ITypedEntityQuery Where(this ITypedEntityQuery source, IPredicateDescription predicateDescription); VB Public Shared ITypedEntityQuery Where(Me ITypedEntityQuery source, _ IPredicateDescription predicateDescription) The query that will be executed is exactly the same regardless of which extension method is used. Both queries are Product queries. The critical difference is that the compiler can't determine the type of the query in the second case. That's why it resolved to a query typed as ITypedEntityQuery, which implements IQueryable, instead of IEntityQuery<T> which implements IQueryable<T>. There are two ramifications: Completely dynamic queries, those typed as ITypedEntityQuery, must be executed with one of the EntityManager.Execute methods instead of ToList(). The compiler declares that the type of the dynamic query execution result is IEnumerable instead of IEnumerable<T>. The following example illustrates: C# // IQueryable<T> implementation var query1 = anEntityManager.Products.Where(p3); IEnumerable<Product> products1 = query1.ToList(); // IQueryable implementation var baseQuery = EntityQuery.CreateQuery(typeof(Product), anEntityManager); var query2 = baseQuery.Where(p1.And(p2)); // can't call query2.ToList() because query2 is not an IQueryable<T> IEnumerable results = anEntityManager.ExecuteQuery(query2); // next line is required in order to IEnumerable<Product> products2 = results.Cast<Product>(); VB ' IQueryable<T> implementation Dim query1 = anEntityManager.Products.Where(p3) Dim products1 As IEnumerable(Of Product) = query1.ToList() ' IQueryable implementation Dim baseQuery = EntityQuery.CreateQuery(GetType(Product), anEntityManager) Dim query2 = baseQuery.Where(p1.And(p2)) ' can't call query2.ToList() because query2 is not an IQueryable<T> Dim results As IEnumerable = anEntityManager.ExecuteQuery(query2) ' next line is required in order to Dim products2 As IEnumerable(Of Product) = results.Cast(Of Product)()
Combine predicates with PredicateBuilder Last modified on April 26, 2011 14:27 Contents Two kinds of predicate Why do we need the PredicateBuider? PredicateBuilder APIs PredicateBuilder examples Debugging PredicateBuilder methods Lazy typed PredicateDescriptions Convert a PredicateDescription to a "predicate expression" The IdeaBlade.Linq.PredicateBuilder provides the core functionality to build dynamic LINQ Where clauses out of multiple predicates. This topic explores predicate combinations and delves further into PredicateBuilder and PredicateDescription capabilities.
Two kinds of predicate A predicate is a function that returns true or false for an item it evaluates. A query Where clause takes a predicate that filters items to include in the query result. Predicates come in two forms in DevForce queries. an object of type Expression<Func<T, bool>>. This is referred to as a predicate expression. an object that implements the DevForce IdeaBlade.Core.IPredicateDescription interface such as IdeaBlade.Linq.PredicateDescription. You use the predicate expression form when defining a LINQ query and you know the type of object the predicate will evaluate. You use the IPredicateDescription form when you don't know the type of object to filter at compile time. Why do we need the PredicateBuider? We need PredicateBuilder because combining predicates of either kind is hard in .NET. Let's illustrate with two predicate expressions. C# Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A"); Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.Contains("e"); VB Dim expr1 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("A") Dim expr2 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.Contains("e") It's easy to use either of these expressions independently; it's not obvious how to combine them. C# var query1 = myEntityManager.Customers.Where(expr1); // ok var query2 = myEntityManager.Customers.Where(expr2); // ok var queryBoth = myEntityManager.Customers.Where(expr1 && expr2) // BAD - won't compile. VB Dim query1 = myEntityManager.Customers.Where(expr1) ' ok Dim query2 = myEntityManager.Customers.Where(expr2) ' ok Dim queryBoth = myEntityManager.Customers.Where(expr1 AndAlso expr2) ' BAD - won't compile. The PredicateBuilder can combine two predicates into a third predicate; then we pass that third predicate to the query's Where clause: C# var expr3 = PredicateBuilder.And(expr1, expr2); var queryBoth = myEntityManager.Customers.Where(expr3); // this works VB Dim expr3 = PredicateBuilder.And(expr1, expr2) Dim queryBoth = myEntityManager.Customers.Where(expr3) ' this works Here's a more convenient syntax that uses PredicateBuilder's predicate expression extension methods to do the same thing without mentioning PredicateBuilder explicitly: C# var expr3 = expr1.And(expr2); var queryBoth = myEntityManager.Customers.Where(expr3); // this works VB Dim expr3 = expr1.And(expr2) Dim queryBoth = myEntityManager.Customers.Where(expr3) ' this works PredicateBuilder APIs The PredicateBuilder is a static class with two kinds of static methods: 1. methods that take predicate expression parameters and return a predicate expression - the strongly typed API. 2. methods that take IPredicateDescription parameters and return an IPredicateDescription - the untyped API. The two APIs mirror each other. We'll describe the strongly typed API involving predicate expressions and assume confidently that you can extrapolate to the IPredicateDescription methods. Method Example And PredicateBuilder.And(p1, p2, p3 .. pn) Or PredicateBuilder.Or(p1, p2, p3 .. pn) Not PredicateBuilder.Not(p1) True PredicateBuilder.True<Product>() False PredicateBuilder.False<Product>() p = Predicate Expression, Expression<Func<T, bool>>, where 'T' is the type of the evaluated item. All combined predicates must have the same item type (e.g., Product). The True() and False() methods return predicate expression constants that simply help you jump-start your chaining of PredicateBuilder expressions ... as seen in the examples below. Additional overloads of Or(), And(), and Not() are extension methods that make fluent composition of predicates a little easier: p1.Or(p2) p1.And(p2) p1.Not() The parallel extension methods for IPredicateDescription are located in DynamicQueryExtensions rather than PredicateBuilder. PredicateBuilder examples Here are some examples using the PredicateBuilder predicate expression methods: C# Expression<Func<Product, bool>> p1, p2, p3, p4, bigP; // Sample predicate expressions p1 = p => p.ProductName.Contains("Sir"); p2 = p => p.ProductName.Contains("Cajun"); p3 = p => p.ProductName.Contains("Louisiana"); p4 = p => p.UnitPrice > 20; bigP = p1.Or(p2); // Name contains "Sir" or "Cajun" bigP = p1.Or(p2).Or(p3); // Name contains any of the three bigP = PredicateBuilder.Or(p1, p2, p3); // Name contains any of the 3 bigP = PredicateBuilder.Or(tests); // OR together some tests bigP = p1.And(p4); // "Sir" and price is greater than 20 // Name contains "Cajun" and "Lousiana" and the price is greater than 20 bigP = PredicateBuilder.And(p2, p3, p4); bigP = PredicateBuilder.And(tests); // AND together some tests // Name contains either Sir or Louisiana AND price is greater than 20 bigP = p1.Or(p3).And(p4); // bigP = PredicateBuilder.Not(p1); // Name does not contain "Sir" bigP = PredicateBuilder.True<Product>().And(p1);// same as p1 bigP = PredicateBuilder.False<Product>().Or(p1);// same as p1 // Not useful bigP = PredicateBuilder.True<Product>().Or(p1);// always true bigP = PredicateBuilder.False<Product>().And(p1);// always false VB Dim p1 As Expression(Of Func(Of Product, Boolean)), p2 As Expression(Of _ Func(Of Product, Boolean)), p3 As Expression(Of Func(Of Product, Boolean)), _ p4 As Expression(Of Func(Of Product, Boolean)), bigP As Expression(Of _ Func(Of Product, Boolean)) ' Sample predicate expressions p1 = Function(p) p.ProductName.Contains("Sir") p2 = Function(p) p.ProductName.Contains("Cajun") p3 = Function(p) p.ProductName.Contains("Louisiana") p4 = Function(p) p.UnitPrice > 20 bigP = p1.Or(p2) ' Name contains "Sir" or "Cajun" bigP = p1.Or(p2).Or(p3) ' Name contains any of the three bigP = PredicateBuilder.Or(p1, p2, p3) ' Name contains any of the 3 bigP = PredicateBuilder.Or(tests) ' OR together some tests bigP = p1.And(p4) ' "Sir" and price is greater than 20 ' Name contains "Cajun" and "Lousiana" and the price is greater than 20 bigP = PredicateBuilder.And(p2, p3, p4) bigP = PredicateBuilder.And(tests) ' AND together some tests ' Name contains either "Sir" or "Louisiana" AND price is greater than 20 bigP = p1.Or(p3).And(p4) bigP = PredicateBuilder.Not(p1) ' Name does not contain "Sir" bigP = PredicateBuilder.True(Of Product)().And(p1) ' same as p1 bigP = PredicateBuilder.False(Of Product)().Or(p1) ' same as p1 ' Not useful bigP = PredicateBuilder.True(Of Product)().Or(p1) ' always true bigP = PredicateBuilder.False(Of Product)().And(p1) ' always false Debugging PredicateBuilder methods Put a breakpoint on any of the "bigP" lines and ask the debugger to show you the result as a string. Here is the Immediate Window output for C# bigP = p1.Or(p3).And(p4); VB bigP = p1.Or(p3).And(p4) {p => ((p.ProductName.Contains("Sir") || p.ProductName.Contains("Louisiana")) && (p.UnitPrice > Convert(20)))} Lazy typed PredicateDescriptions We usually specify the item type when we create a PredicateDescription. That isn't strictly necessary. We can postpone determining the query type until the PredicateDescription is used in a query. For example: C# var p1 = new PredicateDescription("UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24); var p2 = new PredicateDescription("Discontinued", FilterOperator.IsEqualTo, true); var p3 = p1.And(p2); //unit price >= 24 and discontinued. VB Dim p1 = New PredicateDescription("UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24) Dim p2 = New PredicateDescription("Discontinued", FilterOperator.IsEqualTo, True) Dim p3 = p1.And(p2) ' unit price >= 24 and discontinued. The variables p1, p2, p3 are well-defined but do not have embedded query types. They can be used in any query for which they are appropriate, e.g., C# var productQuery = mgr.Products.Where(p3); var internationalProductQuery = mgr.InternationalProducts.Where(p3); // but not var customerQuery = mgr.Customers.Where(p3); VB Dim productQuery = mgr.Products.Where(p3) Dim internationalProductQuery = mgr.InternationalProducts.Where(p3) ' but not Dim customerQuery = mgr.Customers.Where(p3) The customerQuery compiles but throws at runtime because Customer doesn't have a UnitPrice or Discontinued property. The productQuery and internationalQuery will run because these properties are defined for Product and InternationalProduct. As a general rule it is best to embed the query type in the PredicateDescription when you define it unless you have a compelling reason to do otherwise. Filtering on anonymous types within a complex query is one such compelling reason. Convert a PredicateDescription to a "predicate expression" When a dynamic PredicateDescription has an embedded query type information, you can convert it into a strongly typed predicate expression using the ToLambdaExpression() method. Once converted, the expression is ready to use in a strongly-typed query, the kind of query you typically write in an application. The following code illustrates: C# // p1 was the first PredicateDescription that filtered for UnitPrice >= 24 var exprFunc = (Expression<Func<Product, bool>>)p1.ToLambdaExpression(); var filterQuery = anEntityManager.Products.Where(exprFunc); var results = anEntityManager.ExecuteQuery(filterQuery); VB ' p1 was the first PredicateDescription that filtered for UnitPrice >= 24 Dim exprFunc = CType(p3.ToLambdaExpression(), Expression(Of Func(_ Of Product, Boolean))) Dim filterQuery = _em1.Products.Where(exprFunc) Dim results = _em1.ExecuteQuery(filterQuery) This gambit is only possible when you can pass the PredicateDescription into compiled code that knows the target type ... as happened here. Such occasions are rare but they occur and the ToLambdaExpression() method is standing by when the opportunity to convert presents itself.
Create dynamic "OrderBy" clauses Last modified on October 27, 2011 15:29 You can specify the "OrderBy" criteria dynamically with the I deaBlade.Linq.SortSelector when you can't determine the sort order at compile time. You create an instance of the SortSelector class by specifying the type of object to sort, the property to sort, and its sort direction. The OrderBySelector extension method makes it easy to use a SortSelector in a standard LINQ query you would otherwise have used OrderBy, OrderByDescending, ThenBy or ThenByDescending. For example: C# var sort = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); var customers = myEntityManager.Customers.OrderBySelector(sort).ToList();
// compare with the strongly-typed LINQ equivalent var customers = myEntityManager.Customers.OrderByDescending(c => c.Country).ToList(); VB Dim sort = New SortSelector(GetType(Customer), "Country", ListSortDirection.Descending) Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList()
' compare with the strongly-typed LINQ equivalent Dim customers = myEntityManager.Customers.OrderByDescending(Function(c) c.Country).ToList() A single SortSelector instance can specify multiple sort properties, each with its own sort direction. You add sort criteria with the ThenBy method. C# var sort = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); sort = sort.ThenBy("CompanyName"); // by default ListSortDirection is Ascending if not specified. var customers = myEntityManager.Customers.OrderBySelector(sort).ToList();
// compare with the strongly-typed LINQ equivalent var customers = myEntityManager.Customers .OrderByDescending(c => c.Country) .ThenBy(c => c.CompanyName) .ToList(); VB Dim sort = New SortSelector(GetType(Customer), "Country", ListSortDirection.Descending) sort = sort.ThenBy("CompanyName") ' by default ListSortDirection is Ascending if not specified. Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList()
' compare with the strongly-typed LINQ equivalent Dim customers = myEntityManager.Customers .OrderByDescending(Function(c) c.Country) .ThenBy(Function(c) c.CompanyName) .ToList() SortSelectors can also be combined with an overload of the ThenBy method or with the static Combine method. C# // create two SortSelectors var sort1 = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); var sort2 = new SortSelector(typeof(Customer), "CompanyName");
// combine them using either of the following two mechanisms. var sort = sort1.ThenBy(sort2); // or var sort = SortSelector.Combine(new[] { sort1, sort2 }); VB ' create two PropertySelectors Dim sort1 = New SortSelector(GetType(Customer), _ "Country", ListSortDirection.Descending) Dim sort2 = New SortSelector(GetType(Customer), "CompanyName")
' combine them using either of the following two mechanisms. Dim sort = sort1.ThenBy(sort2) ' or Dim sort = SortSelector.Combine( { sort1, sort2 })
The syntax and behavior of a SortSelector are similar to the PredicateDescription used in a Where clause as described here and here. For example, you can delay specifying the type of entity to sort until you actually use the SortSelector. C# // Type not specified; it is determined when used var sort = new SortSelector("Country", ListSortDirection.Descending); var customers = myEntityManager.Customers.OrderBySelector(sort).ToList(); VB ' Type not specified; it is determined when used Dim sort = New SortSelector("Country", ListSortDirection.Descending) Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList() Such lazy-typing can be useful when sorting anonymous types. PropertySortSelector is deprecated The IdeaBlade.Linq.PropertySortSelector was the less-capable class for ordering query result dynamically in versions of DevForce prior to v.6.1.0. That class has been deprecated in favor of SortSelector and will be removed from the product at a future date.
Create dynamic "Select", "SelectMany" and "GroupBy" clauses Last modified on October 27, 2011 15:29 Contents Select examples o Select into a dynamic type o The DevForce dynamic type o The memory footprint of dynamic types SelectMany example GroupBy example PropertyProjectionSelector is deprecated You can construct Select, SelectMany and GroupBy clauses dynamically with the IdeaBlade.Linq.ProjectionSelector when you can't specify them at compile time.
The LINQ Select, SelectMany and GroupBy clauses change the shape of a query result. Queries that uses these clauses are called "projections" and we refer to them as "projection clauses" in the following discussion. When you need to construct a projection query but you don't know the types involved at compile time, you can create an instance of the ProjectionSelector class and pass in the type information at runtime. Select examples
When creating a ProjectionSelector class you specify the properties to select (AKA, "project"). You can also specify the type of object to which the properties belong ... if you know the type ... or delay type identification until you use the selector. Consider a simple example in which we query for Customers and return ("project") their CompanyNames. If we knew we were going to do this at compile time, we'd write: C# var companyNames = anEntityManager.Customers.Select(c => c.CompanyName).ToList(); VB Dim companyNames = anEntityManager.Customers.Select(Function(c) c.CompanyName).ToList() But we don't know. So we write a general purpose function, DoSelect, that is capable of projecting an arbitrary property belonging to an arbitrary entity type. To see it in action, we call it with the Customer type and specify the CompanyName type as before. C# public IEnumerable DoSelect(EntityManager manager, Type entityType, string propertyName) { var selector = new ProjectionSelector(propertyName);
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery.Select(selector); // selector learns its type from rootQuery var results = query.Execute(); // synchronous query execution return results; }
// The company names of every Customer var companyNames = DoSelect(anEntityManager, typeof(Customer), "CompanyName"); VB Public Function DoSelect(ByVal manager As EntityManager, ByVal entityType As Type, ByVal propertyName As String) As IEnumerable
Dim selector = New ProjectionSelector(propertyName)
Dim rootQuery = EntityQuery.Create(entityType, manager) Dim query = rootQuery.Select(selector) ' selector learns its type from rootQuery Dim results = query.Execute() ' synchronous query execution Return results End Function
' The company names of every Customer Dim companyNames = DoSelect(anEntityManager, GetType(Customer), "CompanyName") Any property of a class can be projected by passing in its property name, or as in the next example, passing in a nested property name. C# // The company names of every Order's Customer var companyNames = DoSelect(anEntityManager, typeof(Order), "Customer.CompanyName"); VB ' The company names of every Order's Customer Dim companyNames = DoSelect(anEntityManager, GetType(Order), "Customer.CompanyName") Select into a dynamic type We can use the Select clause to project multiple values into a single instance of a DevForce dynamic type (we explain dynamic types below). In our next example, we get a few Products, find their related Category entities, and project two of the Category properties into a dynamic type. For ease of exposition, we specify Product, Category and its properties in the code. If you actually knew the types and properties at design time you'd use strongly typed LINQ statements and wouldn't bother with the ProjectionSelector. We trust you appreciate our true intention. C# IProjectionSelector selector = new ProjectionSelector("Category.Name", "CatName"); selector = selector.Combine("Category.Description", "CatDesc");
var rootQuery = EntityQuery.Create(typeof(Product), anEntityManager); var query = rootQuery.Select(selector); var results = query.Execute(); VB Dim selector = New ProjectionSelector("Category.Name", "CatName") as IProjectionSelector selector = selector.Combine("Category.Description", "CatDesc"))
Dim rootQuery = EntityQuery.Create(GetType(Product), anEntityManager) Dim query = rootQuery.Select(selector) Dim results = query.Execute() A perhaps more graceful syntax, especially when there are many properties, might be: C# var selector = new AnonymousProjectionSelector() .Combine("Category.Name", "CatName") .Combine("Category.Description", "CatDesc");
var rootQuery = EntityQuery.Create(typeof(Product), anEntityManager); var query = rootQuery.Select(selector); var results = query.Execute(); VB Dim selector = New AnonymousProjectionSelector() .Combine("Category.Name", "CatName") .Combine("Category.Description", "CatDesc")
Dim rootQuery = EntityQuery.Create(typeof(Product), anEntityManager) var query = rootQuery.Select(selector) Dim results = query.Execute()
This form draws proper attention to the AnonymousProjectionSelector and the use of the Combine extension method to build up the properties of the projection. The "CatName" and "CatDesc" arguments are the aliases for the projected nested properties; they become the names of the two properties of the DevForce dynamic type objects returned in the results. Finally, a quite general projection function which suggests the potential for this approach: C# public IEnumerable DoSelect( EntityManager manager, Type entityType, params string[] propertyNames) { var selector = new AnonymousProjectionSelector(); foreach (var name in propertyNames) { var alias = name.Replace(".", "_"); selector = selector.Combine(name, alias); }
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery.Select(selector); // selector learns its type from rootQuery var results = query.Execute(); // synchronous query execution return results; } VB Public Function DoSelect(ByVal manager As EntityManager, _ ByVal entityType As Type, ByVal ParamArray propertyNames As String()) As IEnumerable
Dim selector = new AnonymousProjectionSelector() For Each name As String In propertyNames Dim alias = name.Replace(".", "_") selector = selector.Combine(name, alias) Next
Dim rootQuery = EntityQuery.Create(entityType, manager) Dim query = rootQuery.Select(selector) ' selector learns its type from rootQuery Dim results = query.Execute() ' synchronous query execution Return results
End Function
The DevForce dynamic type The DevForce dynamic type returned in query results may be treated exactly like a standard .NET anonymous type. DevForce cannot return an actual .NET anonymous type because the compiler insists that anonymous types be known at compile time. The dynamic types DevForce creates are not defined until runtime. Therefore, DevForce dynamically creates a type that has the same semantics as an anonymous type by emitting IL at runtime. A DevForce dynamic type has one additional benefit: it is a public class which means that Silverlight controls can bind to it. Silverlight controls can't bind to .NET anonymous classes becauses they are declared internal. You access properties of a dynamic type much as you would properties of a .NET anonymous type as we see in the following two examples. The first makes use of the dynamic keyword introduced in .NET 4. C# foreach (dynamic item in results) { String categoryName = (String) item.CatName; String categoryDescription = (String)item.CatDesc; } VB For Each item As dynamic In results Dim categoryName As String = CType(item.CatName, String) Dim categoryDescription As String = CType(item.CatDesc, String) Next item The second uses the DevForce IdeaBlade.Core.AnonymousFns helper class to deconstruct a dynamic type into an object array. C# var elementType = results.Cast<Object>().FirstOrDefault().GetType(); var items = AnonymousFns.DeconstructMany(results, false, elementType);
foreach (var item in items) { String categoryName = (String) item[0]; String categoryDescription = (String)item[1]; } VB Dim elementType = results.Cast(Of Object)().FirstOrDefault().GetType() Dim items = AnonymousFns.DeconstructMany(results, False, elementType)
For Each item In items Dim categoryName As String = CType(item(0), String) Dim categoryDescription As String = CType(item(1), String) Next item The memory footprint of dynamic types Dynamic projections are convenient but should be used with care. Every .NET type definition consumes memory that cannot be reclaimed by the .NET garbage collector. This is as true of the dynamic types that DevForce creates as it is of .NET anonymous types. Queries that return the same dynamic type "shape" - the same properties, of the same types, in the same order, with the same names - are not a problem; dynamic type definitions are cached and a type with matching shape is reused. But it's possible for client applications to create an endless variety of dynamic type shapes. While the amount of memory consumed by a single type is tiny and almost never an issue on a single client machine, each of these types is also created on the EntityServer where the query is executed. A long-running EntityServer might accumulate large numbers of distinct dynamic type definitions. The server could run out of memory if it saw millions of different anonymous types in which case you'll have to recycle the server periodically if this becomes a problem. It's not a great risk but we felt we should mention it. SelectMany example A dynamic implementation of the LINQ SelectMany operation also makes use of the ProjectionSelector. The following contrived example shows how to use SelectMany to get the OrderDetails associated with the first five Products in the database. It relies upon a peculiar function, DoSelectMany, that gets an arbitrary set of related entities from the first five instance of some kind of entity. C# public IEnumerable DoSelectMany( EntityManager manager, Type entityType, string propertyName) { var selector = new ProjectionSelector(propertyName);
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery .Take(5) // reduce the result size for exposition. .SelectMany(selector); var results = query.Execute(); return results; }
var orderDetails = DoSelectMany(anEntityManager, typeof(Product), "OrderDetails") .Cast<OrderDetail>() // convert IEnumerable to IEnumerable<OrderDetails> .ToList(); VB Public Function DoSelectMany(ByVal manager As EntityManager, _ ByVal entityType As Type, ByVal propertyName As string) As IEnumerable
var selector = New ProjectionSelector(propertyName)
Dim rootQuery = EntityQuery.Create(entityType, mManager) Dim query = rootQuery .Take(2) ' reduce the result size for exposition. .SelectMany(selector) Dim results = query.Execute() Return results
End Function
Dim orderDetails = DoSelectMany(anEntityManager, GetType(Product), "OrderDetails") .Cast<OrderDetail>() ' convert IEnumerable to IEnumerable<OrderDetails> .ToList() GroupBy example
A dynamic implementation of the LINQ GroupBy operation also makes use of the ProjectionSelector. In the following example, the SimpleGroupBy function queries for every instance of a given type and groups the results by one of the properties of that type. C# public IEnumerable SimpleGroupBy(EntityManager manager, Type entityType, string groupByProperty) { var groupBy = new ProjectionSelector(groupByProperty, alias);
var query = EntityQuery.Create(entityType, manager) .GroupBy(groupBy); var result = manager.ExecuteQuery(query); return result; }
// Static equivalent: anEntityManager.Products.GroupBy(_ => _.Category.CategoryName); var result = SimpleGroupBy(anEntityManager, typeof(Product), "Category.CategoryName");
var productGrps = result.Cast<IGrouping<String, Product>>().ToList(); VB Public Function SimpleGroupBy(ByVal manager As EntityManager, ByVal entityType As Type,ByVal groupByProperty As String) _ As IEnumerable
Dim groupBy = new ProjectionSelector(groupByProperty, alias)
Dim query = EntityQuery.Create(entityType, manager) .GroupBy(groupBy) Dim result = manager.ExecuteQuery(query) Return result End Function
' Static equivalent: anEntityManager.Products.GroupBy(Function(_) _.Category.CategoryName) Dim result = SimpleGroupBy(anEntityManager, GetType(Product), "Category.CategoryName")
Dim productGrps = result.Cast(Of IGrouping(Of String, Product))().ToList()
PropertyProjectionSelector is deprecated The IdeaBlade.Linq.PropertyProjectionSelector is now obsolete, having been superseded by the ProjectionSelector. I
Query using property navigation Querying using property navigation is a convenient syntax for accessing data from related entities. Consider these familiar scenarios: Get the line items (OrderDetails) of an order Get the name of the order's parent customer Get the parent customer's other orders In DevForce you might write: anOrder.OrderDetails anOrder.Customer.CompanyName anOrder.Customer.Orders In each case, we want information (OrderDetails, Customers) related to a single entity (Order). The desired information exists somewhere in the entitys object graph the network of other entities that are related to it. Property navigation helps us traverse the network to reach related entities. The third example shows how you can chain navigation properties together. The statement anOrder.Customer.Orders navigates from a particular Order up to its parent Customer and then down to all of the parent Customer's other Orders. We call this "walking the object graph". There are two kinds of navigation property: 1. Reference Navigation returns at most a single entity, typically a "parent" entity as in anOrder.Customer. 2. Collection Navigation returns a list of related entities, typically the "children" of this entity as in aCustomer.Orders and anOrder.OrderDetails. Navigation properties and data retrieval Any time a navigation property is accessed, DevForce needs to determine whether to try to fetch the related entity or entities from the backend datastore or to use previously retrieved data. In general, the first time any navigation property on an entity is accessed, data will be retrieved from the database and all subsequent attempts to access the property will use the local cache. Navigation properties and data retrieval can be modified in several ways.
Navigation properties Every navigation property on an entity has a corresponding EntityReferenceBase that in turn has an IsLoaded property that is settable. This may be set on a per entity/per navigation property basis. For example to force the next access of myCustomer.Orders to go to the database the following code would be used. C# Customer.PropertyMetadata.Orders.GetEntityReference(myCustomer).IsLoaded = false; VB Customer.PropertyMetadata.Orders.GetEntityReference(myCustomer).IsLoaded = False Every navigation property has its default 'loading' behavior determined by a ReferenceStrategy that may be set on the appropriate EntityProperty. This is a global setting for this property across all entities. For example to force every access of any Customer's Orders property to go to the database and to have the results of this navigation overwrite any local changes the following code would be used. C# Customer.PropertyMetadata.Orders.ReferenceStrategy = new EntityReferenceStrategy(EntityReferenceLoadStrategy.Load, MergeStrategy.OverwriteChanges); VB Customer.PropertyMetadata.Orders.ReferenceStrategy = _ New EntityReferenceStrategy(EntityReferenceLoadStrategy.Load, _ MergeStrategy.OverwriteChanges) As shown in the example above, a ReferenceStrategy is itself is made up of a EntityReferenceLoadStrategy and a MergeStrategy. The EntityReferenceLoadStrategy determines under what conditions data should be 'loaded' ( i.e. retrieved from the database) and the MergeStrategy determines how that data, if loaded, gets merged into the local entity cache. LoadStrategy Meaning Lazy first access to the property should go to the database; subsequent accesses should use the local cache Load for every access always try to load from the database DoNotLoad never try to load from the database; property access always looks in cache. You can load the entities manually as described below. The default LoadStrategy is Lazy; the default MergeStrategy is PreserveChanges. These concepts are also discussed under the Entity cache and Entity metadata topics. Loaded state The ReferenceStrategy governs the behavior of a navigation property for all instances of the entity type. You can tweak the current state of a particular entity's navigation property. For example, you can force the "cust" Customer entity to act as if its orders have already been loaded into cache: C# Customer.PropertyMetadata.Orders .GetEntityReference(cust) .IsLoaded = true; VB Customer.PropertyMetadata.Orders _ .GetEntityReference(cust) _ .IsLoaded = True If you didn't know the entity type or property name at compile time, you could write a general function to do this. The pertinent statement would be something like: C# anEntity.EntityAspect.EntityMetadata .NavigationProperties .First(p => p.Name == somePropertyName) .GetEntityReference(anEntity) .IsLoaded = true; VB anEntity.EntityAspect.EntityMetadata _ .NavigationProperties _ .First(Function(p) p.Name = somePropertyName) _ .GetEntityReference(anEntity) _ .IsLoaded = True This statement would have the same effect as before if anEntity == cust and somePropertyName == "Orders". Force loading If you set an entity type's LoadStrategy to DoNotLoad as we did above, you'll have to manually manage the loading of the property's data. Sometimes you want to force a navigation property to refresh. Here's one way to force a (re)load of the example customer's orders: C# Customer.PropertyMetadata.Orders .GetEntityReference(cust) .Load(MergeStrategy.PreserveChanges); VB Customer.PropertyMetadata.Orders _ .GetEntityReference(cust) _ .Load(MergeStrategy.PreserveChanges) The navigation property load operation runs asynchronously in Silverlight, synchronously elsewhere. It may be easier simply to query cust's orders directly from the database after which cust.Orders will find them in cache. Parent-child navigation properties Last modified on March 22, 2011 11:56 So far weve considered only navigation properties that return a single entity. Navigation properties can return many entities. The myOrder.OrderDetails navigation property, for example, returns the many line items of a single order. Navigation properties that return multiple entities are sometimes termed parent->child or principal->dependent properties. In these cases, the property belongs to the parent entity such as Order and it returns child entities such as OrderDetail entities. The navigation property returns child entities in a RelatedEntityList<T> collection. For example, the Order.OrderDetails property returns its OrderDetail children in a concrete collection, RelatedEntityList<OrderDetail>. A brief example I write and run the following statements and learn that there are three line items in the collection owned by anOrder: C# Order anOrder = _em1.Orders.FirstOrNullEntity(); RelatedEntityList<OrderDetail> lineItems = anOrder.OrderDetails; VB Dim anOrder As Order = _em1.Orders.FirstOrNullEntity() Dim lineItems As RelatedEntityList(Of OrderDetail)=anOrder.OrderDetails We decide to increase the quantity ordered for the first OrderDetail as follows. C# var firstItem = lineItems[0]; firstItem.Quantity = 10; VB Dim firstItem As OrderDetail = lineItems(0) firstItem.Quantity = 10
Adding and Removing Related Objects Add() The Add() method takes a parameter of the type contained by the collection (e.g., an Order). C# Order anOrder = new Order(); anOrder.OrderDate = DateTime.Today; anOrder.FreightCost = Convert.ToDecimal(999.99); anEmployee.Orders.Add(anOrder); VB Dim anOrder As New Order() anOrder.OrderDate = Date.Today anOrder.FreightCost = Convert.ToDecimal(999.99) anEmployee.Orders.Add(anOrder) Invoking Add() adds the supplied item to the collection. If the relation between the parent and child types is 1-to-many and the supplied item is currently associated with a different parent, then Add() simultaneously removes it from the corresponding collection of the other parent. The equivalent result on table rows in a relational database is that the child entitys foreign key value is changed. Note that, in the above snippet, we did not need to set the SalesRep property of the new Order to the Employee whom we wanted to become its parent: C# // anOrder.SalesRep = anEmployee; //don't need this; Add() will handle it VB anOrder.SalesRep = anEmployee '' don't need this; Add() will handle it Invocation of the Add() method on anEmployee.Orders produced the equivalent result. Remove() Remove() also takes a parameter of the type contained by the collection. It dissociates the indicated instance from the collections parent. Speaking again of the equivalent result on table rows in a relational database, the child entitys foreign key value is set to null. 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 Orders EntityAspect.Remove()or EntityAspect.Delete() methods, as appropriate. Add() and Remove () on many-to-many navigation properties You can also use Add() and Remove () on many-to-many navigation collections generated by the Entity Data Model. You get these in your Entity Data Model when two entities are linked by a many-to-many linking table that has "no payload"; that is, no columns other than the two foreign keys (which also form a composite primary key). An example would be an Employee linked to a Territory by means of an EmployeeTerritory table whose composite primary key consists of the two foreign keys EmployeeId and TerritoryId, and which has no other columns. When you have such an association, invoking Add() on the many-to-many navigation property creates (in the EntityManager cache) the necessary linking object in the EntitySet for the linking objects. Note that those objects are not exposed in the conceptual model, and are never manipulated directly by you. Remove() marks as deleted the linking object that formerly connected the two entities in the many-to-many relationship. Both changes the insertion of a new linking object or the deletion of an existing one are propagated to the back-end data store upon the execution of SaveChanges() on the governing EntityManager. Adding and removing items in custom-coded many-to-many navigation properties You can (and probably will) also have in your model many-to-many associations involving linking entities that do have payload. (For example, in the NorthwindIB database, Order links Employees (who act as sales reps) to Customers in a many-to-many relationship.) For these cases, you should add and remove elements to the m-to-m collection (e.g., anEmployee.Customers) by inserting or deleting instances of the linking entity. Since that linking entity is probably significant in its own right (again consider an Order), it likely has properties that need their values set at creation time in any case. For example, the following code will have the indirect effect of adding a new Customer to the Customers collection of anEmployee, but only if the Order being added is for a Customer with which anEmployee is not already linked through some other Order. Otherwise, aCustomer is already in anEmployees Customers collection. (Note: Create is a static method on the Order partial class that the developer extends) C# // May add a Customer to anEmployees Customers collection anOrder = Order.Create(_entityManager, aCustomer, anOrderDate); anEmployee.Orders.Add(anOrder); VB ' May add a Customer to anEmployees Customers collection anOrder = Order.Create(_entityManager, aCustomer, anOrderDate) anEmployee.Orders.Add(anOrder) Similarly, the following code will have the indirect effect of removing aCustomer from the Customers collection of anEmployee, but only if anEmployee has no other Orders for aCustomer. If she does, then aCustomer will remain in her Customers collection. C# // May remove a Customer from anEmployees Customers collection anOrder.EntityAspect.Delete(); VB ' May remove a Customer from anEmployees Customers collection anOrder.EntityAspect.Delete()
Asynchronous navigation properties Applicable to Silverlight primarily Because all data retrieval and save operations in Silverlight are required to be asynchronous, navigation properties must be able to execute asynchronously as well. This means that they must be able to return their results within lambdas or callback methods that will execute at an indeterminate time in the future. What is unusual about this process is that property navigation by its nature must also return an immediate result. If the query needs to execute asynchronously, then this immediate result must have some way of indicating that it is not (yet) a real result and that the 'real' result is coming. This introduces the concepts of a PendingEntity and a PendingEntityList. Consider the following code: (Note that this example has been written to run in a desktop environment, instead of Silverlight, in order to more easily make use of the Console.WriteLine to illustrate the timing of events) C# public void NavigationBasicAsynchronous() { _em1.UseAsyncNavigation = true; // not needed in SILVERLIGHT var query = _em1.Orders.Where(o => o.OrderID == 10248); _query.ExecuteAsync(GotOrder); }
private void GotOrder(EntityQueryOperation<Order> args) { if (args.Error != null) { Console.WriteLine(args.Error.Message); } else { // Retrieve a single related entity using a scalar navigation property Order targetOrder = (Order)args.Results.ToList()[0]; Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString()); targetOrder.Customer.EntityAspect.PendingEntityResolved += Customer_PendingEntityResolved; Customer aCustomer = targetOrder.Customer; Console.WriteLine("Customer (from GotOrders): {0}", aCustomer.CompanyName); // Retrieve a collection of related entities using a //collection navigation properties targetOrder.OrderDetails.PendingEntityListResolved += OrderDetails_PendingEntityListResolved; } }
private void PromptToContinue() { Console.WriteLine(); Console.WriteLine("Press ENTER to continue..."); Console.ReadLine(); } VB Public Sub NavigationBasicAsynchronous() ResetEntityManager(_em1) _em1.UseAsyncNavigation = True Dim query As IEntityQuery(Of Order) = _ _em1.Orders.Where(Function(o) o.OrderID = 10248) _em1.ExecuteQueryAsync(Of Order)(query, AddressOf GotOrder, Nothing) PromptToContinue() End Sub
Private Sub GotOrder(ByVal args As EntityQueryOperation(Of Order)) If args.Error IsNot Nothing Then Console.WriteLine(args.Error.Message) Else ' Retrieve a single related entity using a scalar navigation property Dim targetOrder As Order = CType(args.Results.ToList()(0), Order) Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString()) AddHandler targetOrder.Customer.EntityAspect.PendingEntityResolved, _ AddressOf Customer_PendingEntityResolved Dim aCustomer As Customer = targetOrder.Customer Console.WriteLine("Customer (from GotOrders): {0}", aCustomer.CompanyName) ' Retrieve a collection of related entities ' using a collection navigation property AddHandler targetOrder.OrderDetails.PendingEntityListResolved, _ AddressOf OrderDetails_PendingEntityListResolved Console.WriteLine("OrderDetails retrieved: {0}", _ targetOrder.OrderDetails.ToList().Count) End If End Sub
Private Sub Customer_PendingEntityResolved(ByVal sender As Object, _ ByVal e As PendingEntityResolvedEventArgs) Dim customer As Customer = CType(e.ResolvedEntity, Customer) Console.WriteLine( _ "Customer (from Customer_PendingEntityResolved): {0}", customer.CompanyName) End Sub
Private Sub OrderDetails_PendingEntityListResolved(ByVal sender _ As Object, ByVal e As PendingEntityListResolvedEventArgs(Of OrderDetail)) Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count) End Sub
Private Sub ResetEntityManager(ByVal em As EntityManager) em.Clear() em.UseAsyncNavigation = False End Sub In the methods 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, (and only) 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 cant simply say this C# Order anOrder = _em1.Orders.FirstOrNullEntity(); VB Dim anOrder As Order = _em1.Orders.FirstOrNullEntity() , 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. 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. To retrieve a scalar result, such as First(), Single(), Count(), Sum() and others, you may also use the AsScalarAsync() extension on IEntityQuery<T> to convert your query into a scalar query which can be executed asynchronously. C# ... var query = _em1.Orders.Where(o => o.OrderID == 10248); query.ExecuteAsync(GotOrder); ... }
private void GotOrder(EntityQueryOperation<Order> args) { if (args.Error != null) { Console.WriteLine(args.Error.Message); } else { // Retrieve a single related entity using a scalar navigation property Order targetOrder = (Order)args.Results.ToList()[0]; Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString()); } } VB ... Dim query As IEntityQuery(Of Order) = _ _em1.Orders.Where(Function(o) o.OrderID = 10248) _em1.ExecuteQueryAsync(Of Order)(query, AddressOf GotOrder, Nothing) ... End Sub
Private Sub GotOrder(ByVal args As EntityQueryOperation(Of Order)) If args.Error IsNot Nothing Then Console.WriteLine(args.Error.Message) Else ' Retrieve a single related entity using a scalar navigation property Dim targetOrder As Order = CType(args.Results.ToList()(0), Order) Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString()) End If End Sub In this case, since were using the primary key to fetch our Order, we know that args.Result will contain at most one entity; so we simply cast it into an Order and proceed. To get the Customer related to that Order (refer back to the full snippet), we set up a handler for the PendingEntityResolved event of the Customer navigation property, targetOrder.Customer. Then to initiate the asynchronous retrieval of that customer, we reference it in a code statement: C# Customer aCustomer = targetOrder.Customer; VB Dim aCustomer As Customer = targetOrder.Customer We included a call to Console.WriteLine() immediately following the above statement just to show that the desired Customer simply isnt going to be available at that point. The statement will write out a blank for the Customers CompanyName. Where we will get results is in the Customer_PendingEntityResolved handler: C# void Customer_PendingEntityResolved(object sender, PendingEntityResolvedEventArgs e) { Customer customer = (Customer)e.ResolvedEntity; Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}", customer.CompanyName); } VB Private Sub Customer_PendingEntityResolved(ByVal sender As Object, _ ByVal e As PendingEntityResolvedEventArgs) Dim customer As Customer = CType(e.ResolvedEntity, Customer) Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}", _ customer.CompanyName) End Sub Asynchronous collection navigation properties For navigation properties that return a collection, DevForce provides a PendingEntityListResolved event, similar to the PendingEntityResolved event weve just discussed: C# private void GotOrder(EntityQueryOperation<Order> args) { // Retrieve a collection of related entities usin // a collection navigation property Order targetOrder = (Order)args.Results.ToList()[0]; targetOrder.OrderDetails.PendingEntityListResolved += new EventHandler<PendingEntityListResolvedEventArgs<OrderDetail>>( OrderDetails_PendingEntityListResolved); }
void OrderDetails_PendingEntityListResolved(object sender, PendingEntityListResolvedEventArgs<OrderDetail> e) { Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count); } VB Private Sub GotOrder(ByVal args As EntityQueryOperation(Of Order)) ' Retrieve a collection of related entities usin ' a collection navigation property Dim targetOrder As Order = CType(args.Results.ToList()(0), Order) AddHandler targetOrder.OrderDetails.PendingEntityListResolved, _ AddressOf OrderDetails_PendingEntityListResolved End Sub
Private Sub OrderDetails_PendingEntityListResolved(ByVal sender As Object, _ ByVal e As PendingEntityListResolvedEventArgs(Of OrderDetail)) Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count) End Sub When we run the full snippet, the code displays the following results in the Console window:
The output line Press ENTER to continue.. comes from the utility method PromptToContinue(), which executes synchronously and immedately. Then we see reflected back the OrderID of the retrieved Order; the non-existent CompanyName of the not-yet- retrieved, related Customer; the CompanyName of the Customer written after its retrieval by the Customer_PendingEntityResolved callback method; and the display of OrderDetails retrieved, written by the OrderDetails_PendingEntityListResolved method. Using an anonymous method for navigation property callback If youre working in C#, you can also use inline, anonymous methods for your ExecuteQueryAsync() callbacks: C# public void NavigationBasicAsynchronousAnonymousCallback() { _em1.UseAsyncNavigation = true; IEntityQuery<Order> query = _em1.Orders.Where(o => o.OrderID == 10248); _em1.ExecuteQueryAsync<Order>( query, // IEntityQuery<Order> (args) => { // AsyncCompletedCallbac Console.WriteLine("Order: {0}", // " ((Order)args.Results.ToList()[0]).OrderID); // " }, // " null // UserState object ); PromptToContinue(); } VB Public Sub NavigationBasicAsynchronousAnonymousCallback() _em1.UseAsyncNavigation = True Dim query As IEntityQuery(Of Order) = _ _em1.Orders.Where(Function(o) o.OrderID = 10248) _em1.ExecuteQueryAsync(Of Order)(query, Sub(args) _ Console.WriteLine("Order: {0}", (CType(args.Results.ToList()(0), _ Order)).OrderID), Nothing) ' UserState object - " - " - " PromptToContinue() End Sub These are handy when the logic to be included in the callback isnt too involved. Deferred retrieval When does the entitymanager fetch myOrders 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 customers headquarter address, the sales reps address and manager, and each products 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? We cannot know. Fortunately, we dont have to know. We dont have to know if we can be certain of continuous connection to the data source. If we expect the application to run offline, well have to anticipate the related entities well need and pre-fetch them. Well get to this issue later. 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).
Missing objects and the null entity A reference navigation property and some scalar queries return the null entity when there is no entity to return. The null entity (aka, the nullo) is a special version of an entity class that represents the missing object.
In principle, every Order should have a parent Customer. What if a particular noCustOrder doesn't have a parent Customer? What should its Customer navigation property return? Should it return null (Nothing in VB)? Then the statement noCustOrder.Customer.CompanyName will throw a NullReferenceException because the null value doesn't have a CompanyName property. Should we wrap every reference navigation in a giant try/catch block? Or should we follow every entity navigation with a test for null? That might be worse than catching an exception. In some situations - in UI code for example - it is difficult to check whether noCustOrder has a Customer, let alone do something about it. Fortunately an entity reference navigation neither returns a null value nor throws an exception. Instead, when the entitymanager can't find the Customer, it returns the Customer null entity. The null entity The null entity represents the "entity not found". The null entity (known informally as the nullo) is a sentinel object that looks and behaves, for the most part, like a real entity instance. In most respects it is a real entity it has a specific type (e.g., Customer) it has the properties, methods, and events of its type. it belongs to an EntityManager The null entity has four important differences: You can tell its the null entity because noCustOrder.Customer.EntityAspect.IsNullEntity returns true You cannot set a nullo property. You cannot create, delete or save a nullo. There is only one nullo per type in a given EntityManager; you can't have two Customer nullos in the same EntityManager. Actions returning a nullo Among the ways to get a null entity are a reference navigation that cannot find the parent entity a scalar query that fails to find the requested entity ask the EntityManager for it Reference navigation A reference navigation property returns a single entity. The Order.Customer is a reference navigation property returning the order's parent Customer. Because noCustOrder lacks a parent Customer, the statement noCustOrder.Customer returns a Customer nullo. We don't worry about null entities with collection navigations. A collection navigation property returns a list. The list is never null. It is an empty list if there are no related entities. The statement emptyOrder.OrderDetails returns an empty list of OrderDetail. Scalar query A scalar query returns a single object. In the following example, the developer executes a scalar query, requesting that the query return either the first entity found or the null entity if there is no match. There is no Customer with a zero ID. C# cust = manager.Customers .Where(c => c.CustomerID == 0) // doesn't exist .FirstOrNullEntity(); VB cust = (From c in manager.Customers _ Where c = 0 _' doesn't exist Select c) _ .FirstOrNullEntity() Note that query would have returned null - the cust value would be null - if the developer executed the query with FirstOrDefault. Get from manager Although you can't create a nullo, you can ask an EntityManager to give you the nullo of a particular type. C# nullo = manager.GetNullEntity<Customer>(); // the Customer nullo VB nullo = manager.GetNullEntity(Of Customer)() ' the Customer nullo Nullos belong to an EntityManager A null entity always belongs to a particular EntityManager, the same EntityManager as the entity which produced it. The nullo from noCustOrder.Customer belongs to noCustOrder's manager. While there can be only one Customer nullo per EntityManager there can be two nullo Customer instances ... if there are two EntityManagers. The requirement that a null entity belong to cache has an unexpected implication. What if the noCustOrder is not in cache ... if it is detached? Then the statement noCustOrder.Customer returns the null value. You won't get a nullo from a detached entity. A detached entity can't return a nullo because a null entity always belongs to a particular EntityManager and a detached entity doesn't have an EntityManager. DevForce doesn't know what EntityManager to use. All it can do is return null. The default nullo Every entity class defines its own null entity. You don't have to do anything special. By default, its simple data properties return the default value for the property's type. An int returns 0. A string returns the empty string. A property returning a native .NET object type returns a null object of that type; a nullable int returns null. Navigation properties are different. A nullo's reference navigation property returns another nullo. The statement noCustOrder.Customer.Region returns the Region nullo. This makes it safe to chain nullos as in noCustOrder.Customer.Region.Country.CountryName. A collection navigation property returns an empty collection. The statement noCustOrder.Customer.Orders returns an empty list of Orders. ComplexType properties return a real ComplexType object with property values like a nullo. Configure the nullo The CompanyName property of the Customer nullo returns an empty string. Suppose you prefer that it return a canned value such as "N/A" or "[none]". You can change the property values of a nullo if you don't like the defaults by overriding the UpdateNullEntity method in the partial class. Build queries dynamically LINQ expressions are strongly typed and static. They are easy to compose when you know the type of thing you want to query at compile time. But they are difficult to create if you don't know the type until the application runs. Many applications let the user specify the entity type to retrieve, the criteria to filter with, the properties to sort and group by. DevForce dynamic query building components help you build and execute LINQ queries on the fly based on information supplied at runtime.
The problem "LINQ" stands for "Language Integrated Query". LINQ is designed to be written in the same manner as other code, rubbing shoulders with procedural statements in the body of your application source files. C# and Visual Basic are statically typed languages so the main pathway for writing LINQ queries is to compose them statically as strongly typed query objects that implement the IQueryable<T> interface. Standard LINQ queries, and almost of the LINQ examples in the DevForce Resource Center, are static and strongly typed. Here is a typical example: C# IQueryable<Customer> customers = _myEntityManager.Customers; var query = customers.Where(c => c.CompanyName = "Acme"); VB Dim customers As IQueryable(Of Customer) = _myEntityManager.Customers Dim query = customers.Where(Function(c) c.CompanyName = "Acme") The _myEntityManager.Customers expression returns an instance of a query object that implements IQueryable<Customer>. The strongly typed nature of the expression is what allows IntelliSense and the compiler to interpret the remainder of the statement. What if 'T' (Customer in this case) is not known until runtime; from a compiler perspective we no longer have an IQueryable<T> but have instead just an IQueryable. And IQueryable, unfortunately, does not offer any of the extension methods that we think of a standard LINQ; in other words, no Where, Select, GroupBy, OrderBy, Any, Count etc, methods. How do we write a query when we don't have the type available at runtime? C# var typeToQuery = typeof(Customer); IQueryable<???> someCollection = << HOW DO I CREATE AN IQUERYABLE OF "typeToQuery"?>> ; var query = somecollection.Where( << HOW DO I COMPOSE A LAMBDA EXPRESSION WITHOUT A COMPILE TIME TYPE >> ); VB Dim typeToQuery = GetType(Customer) IQueryable(Of ???) someCollection = << HOW DO I CREATE AN IQUERYABLE OF "typeToQuery"?>> Dim query = somecollection.Where(<< HOW DO I COMPOSE A LAMBDA EXPRESSION WITHOUT A COMPILE TIME TYPE >>) A second issue can occur even if we know the type of the query but we have a number of 'where conditions' or predicates, that need to be combined. For example C# Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A"); Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.StartsWith("B"); VB Dim expr1 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("A") Dim expr2 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("B") It turns out that we can use either of these expressions independently, but it isn't obvious how to combine them. C# var query1 = myEntityManager.Customers.Where(expr1); // ok var query2 = myEntityManager.Customers.Where(expr1); // ok var queryBoth = myEntityManager.Customers.Where(expr1 && expr2) // BAD - won't compile. VB Dim query1 = myEntityManager.Customers.Where(expr1) ' ok Dim query2 = myEntityManager.Customers.Where(expr1) ' ok Dim queryBoth = myEntityManager.Customers.Where(expr1 AndAlso expr2) ' BAD - won't compile. The ESQL option You can construct queries on the fly using DevForce "pass-thru" Entity SQL (ESQL). ESQL queries are strings that look like standard SQL, differing primarily (and most obviously) in their references to entity types and properties rather than tables and columns. You can build ESQL queries by concatenating strings that incorporate the runtime query critieria you gleaned from user input. Most developers prefer to construct LINQ queries with DevForce for two reasons: 1. Building syntactically correct ESQL strings can be more difficult to do correctly than writing DevForce dynamic queries. The dynamic query approach affords a greater degree of compiler syntax checking and the structure of the query is more apparent. You often know most of the query at design time; when only some of the query is variable you can mix the statically typed LINQ statements with the dynamic clauses ... as described in the topic detail. 2. ESQL queries must be sent to and processed on the server. You cannot apply ESQL queries to the local entity cache. LINQ queries, on the other hand, can be applied to the database, to the cache, or to both. This is as true of dynamic LINQ queries as it is of static queries. For many developers this is reason enough to prefer LINQ to ESQL. DevForce dynamic query building In order to accomplish this the following pages discuss how to dynamically construct a 'root' query. how to dynamically construct and combine predicates ( LINQ Where clauses). how to dynamically construct sorting conditions ( LINQ OrderBy clauses. how to dynamically construct projections and groupings ( LINQ Select,SelectMany and GroupBy clauses). how to dynamically construct most other LINQ query methods.
Create a completely dynamic query Last modified on April 26, 2011 15:17 Contents EntityQuery.Create LINQ methods where no IQueryable<T> exists Most of the queries created using DevForce will be instances of some query subclass that implements IQueryable<T>. However, there will be cases where we will not be able to determine "T" until runtime. A "completely dynamic" query is one where we do not know the type "T" of the query at compile time. Because these queries cannot implement IQueryable<T> they will instead implement the IQueryable interface.
EntityQuery.Create The static EntityQuery.Create is a non-generic method that creates a new query for a specified type. Here is a function that employs EntityQuery.Create to produce a query that retrieves every instance of a type: C# public EntityQuery GetAll(Type entityType, EntityManager manager) { return EntityQuery.Create(entityType, manager); } VB Public Function GetAll(ByVal entityType As Type, ByVal manager as EntityManager) As EntityQuery Return EntityQuery.Create(entityType, manager) End Function It's a contrived example but you can imagine something like it supporting an application feature that lets users pick the type of entity to retrieve from a list. We use it to retrieve Products and compare this approach with the strongly typed LINQ statement that does the same thing ... when you know that you are always getting Products: C# var queryType = typeof(Product); // the type picked by the user EntityQuery query1 = GetAll(queryType, anEntityManager); EntityQuery<Product> query2 = anEntityManager.Products; VB Dim queryType = GetType(Product) ' the type picked by the user Dim query1 As EntityQuery = GetAll(queryType, anEntityManager) Dim query2 As EntityQuery(Of Product) = anEntityManager.Products These two queries are identical from the standpoint of how they get executed and what they return. The critical difference is that the compiler can't know the type of the query returned in the first case and therefore must return an EntityQuery (which implements IQueryable and ITypedQuery) instead of EntityQuery<T> (which implements IQueryable<T>). This has three ramifications: Completely dynamic queries typically derive from EntityQuery which implements IQueryable and ITypedQuery. These queries must be executed either with the IEntityQuery.Execute extension method or the EntityManager.Execute method; you can't use ToList(). Such queries return IEnumerable results instead of IEnumerable<T>. The following example illustrates these points: C# // IQueryable implementation var query1 = EntityQuery.Create(typeof(Product), anEntityManager); // can't call query1.ToList() because query1 is not an IQueryable<T> IEnumerable results = query1.Execute(); // Have to cast to work with Products IEnumerable<Product> products1 = results.Cast<Product>();
// IQueryable<T> implementation var query2 = anEntityManager.Products; IEnumerable<Product> products2 = query2.ToList(); VB ' IQueryable implementation Dim query1 = EntityQuery.Create(GetType(Product), anEntityManager) ' can't call query1.ToList() because query1 is not an IQueryable<T> Dim results As IEnumerable = query1.Execute() ' Have to cast to work with Products Dim products1 As IEnumerable(Of Product) = results.Cast(Of Product)()
' IQueryable<T> implementation Dim query2 = anEntityManager.Products Dim products2 As IEnumerable(Of Product) = query2.ToList() LINQ methods where no IQueryable<T> exists
LINQ queries, as defined in standard .NET depend on the IQueryable<T> and IEnumerable<T> interfaces. Because a "completely dynamic" query only implements the IQueryable interface there is no way to implement the "standard" LINQ operators. However, DevForce does provide a substitute. DevForce implements a separate set of extension methods that have the same names as the standard LINQ operators but operate on instances of objects that only implement IQueryable instead of IQueryable<T>. ( See IdeaBlade.Linq.QueryableExtensions). That covers many of the familiar LINQ operators and immediate execution methods. Some extension methods require further refinment to operate specifically on instances of DevForce EntityQuery (as opposed to EntityQuery<T>. These methods may be found in IdeaBlade.EntityModel.EntityQueryExtensions along side the other EntityQuery extension methods. They are distinguishable by their first parameter which is of type ITypedEntityQuery. The ITypedEntityQuery interface describes an EntityQuery that is actually an EntityQuery<T> but the type "T" will not be known until runtime and is therefore not available at compile time. This is not something that a developer will usually ever have to concern himself/herself with. The EntityQueryExtensions include the following methods that return an ITypedEntityQuery. EntityQueryExtensions.Where EntityQueryExtensions.OrderBySelector EntityQueryExtensions.Select EntityQueryExtensions.SelectMany EntityQueryExtensions.GroupBy EntityQueryExtensions.Take EntityQueryExtensions.Skip EntityQueryExtensions.Cast EntityQueryExtensions.OfType as well as the following immediate execution methods that take an ITypedEntityQuery and return a scalar result. EntityQueryExtensions.All EntityQueryExtensions.Any EntityQueryExtensions.Contains EntityQueryExtensions.Count An overload of EntityQueryExtensions.AsScalarAsync also extends ITypedEntityQuery so you can call the asynchronous scalar functions {First..., Count, etc.). These overloads differ from the standard LINQ overloads in that none of them involve parameters that are Expression<Func<T, ...>. This is again a ramification of the fact that the generic type "T" is not available at compile time for these methods. These methods instead take either a PredicateDescription, SortSelector, or ProjectionSelector in place of the strongly typed Expression<...>. Create dynamic "Where" clauses The need for the ability to create a dynamic where clause occurs fairly frequently in applications that need to filter data based on a users input. In these cases you don't really know ahead of time what properties a user is going to want to query or what the conditions of the query are likely to be. In fact, you may not even know the type that you will be querying for until runtime. So you will need to be able to compose the query based on the inputs determined at runtime.
Flavors of dynamic where clauses
Ideally, when building a query we want to specify as little dynamically as we have to. The less dynamic a query is the better the compile time checking and intellisense will be for the query. Everything depends upon what we know and when we know it. Do we know the type of the query at compile time but need to wait until runtime to determine the properties to be queried and the conditions regarding these properties? Or do we not even know the type of the query until runtime? Whatever the case, the IdeaBlade.Linq.PredicateBuilder and the IdeaBlade.Linq.PredicateDescription classes are the tools used to deal with compile time uncertainty. As we review the PredicateBuilder API, you will see both typed and untyped overloads for most methods in order to support both compile and runtime scenarios. Whats a predicate? A LINQ where clause is intimately tied to the concept of a "predicate". So what is a predicate and how does it relate to a LINQ where clause. A predicate is a function that evaluates an expression and returns true or false. The code fragment... C# p.ProductName.Contains("Sir") VB p.ProductName.Contains("Sir") ...is a predicate that examines a product and returns true if the products ProductName contains the Sir string. The CLR type of the predicate in our example is: C# Func<Product, bool> VB Func(Of Product, Boolean) Which we can generalize to: C# Func<T, bool> VB Func(Of T, Boolean) This is almost what we need in order to build a LINQ where clause. The LINQ Where extension method is defined as follows: C# public static IQueryable<T> Where<TSource>( this IQueryable<T> source1, Expression<Func<T,bool>> predicate) VB public static IQueryable(Of T) Where(Of TSource) _ (Me IQueryable(Of T) source1, Expression(Of Func(Of T,Boolean)) predicate) Note that the "predicate" parameter above is C# Expression<Func<T, bool>> VB Expression(Of Func(Of T, Boolean)) When we refer to the idea that a "predicate" is needed in order to build a LINQ where clause, what we really mean is that we need to build a "predicate expression", or a Expression that is resolvable to a simple predicate. Create a Where clause with two predicates The snippet below comprises two statements, each of which uses PredicateBuilder.Make to create a PredicateDescription representing a single predicate (filter criteria). C# PredicateDescription p1 = PredicateBuilder.Make(typeof(Product), "UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24); PredicateDescription p2 = PredicateBuilder.Make(typeof(Product), "Discontinued", FilterOperator.IsEqualTo, true); VB Dim p1 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "UnitPrice", _ FilterOperator.IsGreaterThanOrEqualTo, 24) Dim p2 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "Discontinued", _ FilterOperator.IsEqualTo, True) A new query can then be composed and executed by an EntityManager as usual: C# var query = anEntityManager.Products.Where(p1.And(p2)) // The above query is the same as: //var queryb = anEntityManager.Products.Where(p => p.UnitPrice > 24 && p.Discontinued); VB Dim query = anEntityManager.Products.Where(p1.And(p2)) ' The above query is the same as: 'var queryb = anEntityManager.Products.Where(p => p.UnitPrice > 24 && p.Discontinued); We could have combined the individual PredicateDescriptions into another PredicateDescription variable: C# CompositePredicateDescription p3 = p1.And(p2); var query = anEntityManager.Products.Where(p3); VB Dim p3 As CompositePredicateDescription = p1.And(p2) Dim query = anEntityManager.Products.Where(p5) Learn more about combining predicates with PredicateBuilder and PredicateDescription classes. Completely dynamic query The Where clause in the previous examples were still applied to a strongly typed (non- dynamic) IQueryable<Product> implementation. To be more precise, the query root was of type IEntityQuery<Product>. What if we didn't know we were querying for Product at compile type? We'd know the type at runtime but we didn't know it as we wrote the code. We could use the EntityQuery.CreateQuery factory method and pass it the runtime type. CreateQuery returns a nominally-untyped EntityQuery object. C# var queryType = typeof(Product); // imagine this was passed in at runtime. var baseQuery = EntityQuery.CreateQuery(queryType , anEntityManager); var query = baseQuery.Where(p1.And(p2)); VB Dim queryType = GetType(Product) ' imagine this was passed in at runtime. Dim baseQuery = EntityQuery.CreateQuery(queryType , anEntityManager) Dim query = baseQuery.Where(p1.And(p2)) The EntityQuery result of CreateQuery implements ITypedEntityQuery. The Where clause in this example made use of the ITypedEntityQuery extension method instead of the IQueryable<T> extension method: C# public static ITypedEntityQuery Where(this ITypedEntityQuery source, IPredicateDescription predicateDescription); VB Public Shared ITypedEntityQuery Where(Me ITypedEntityQuery source, _ IPredicateDescription predicateDescription) The query that will be executed is exactly the same regardless of which extension method is used. Both queries are Product queries. The critical difference is that the compiler can't determine the type of the query in the second case. That's why it resolved to a query typed as ITypedEntityQuery, which implements IQueryable, instead of IEntityQuery<T> which implements IQueryable<T>. There are two ramifications: Completely dynamic queries, those typed as ITypedEntityQuery, must be executed with one of the EntityManager.Execute methods instead of ToList(). The compiler declares that the type of the dynamic query execution result is IEnumerable instead of IEnumerable<T>. The following example illustrates: C# // IQueryable<T> implementation var query1 = anEntityManager.Products.Where(p3); IEnumerable<Product> products1 = query1.ToList(); // IQueryable implementation var baseQuery = EntityQuery.CreateQuery(typeof(Product), anEntityManager); var query2 = baseQuery.Where(p1.And(p2)); // can't call query2.ToList() because query2 is not an IQueryable<T> IEnumerable results = anEntityManager.ExecuteQuery(query2); // next line is required in order to IEnumerable<Product> products2 = results.Cast<Product>(); VB ' IQueryable<T> implementation Dim query1 = anEntityManager.Products.Where(p3) Dim products1 As IEnumerable(Of Product) = query1.ToList() ' IQueryable implementation Dim baseQuery = EntityQuery.CreateQuery(GetType(Product), anEntityManager) Dim query2 = baseQuery.Where(p1.And(p2)) ' can't call query2.ToList() because query2 is not an IQueryable<T> Dim results As IEnumerable = anEntityManager.ExecuteQuery(query2) ' next line is required in order to Dim products2 As IEnumerable(Of Product) = results.Cast(Of Product)() Combine predicates with PredicateBuilder The IdeaBlade.Linq.PredicateBuilder provides the core functionality to build dynamic LINQ Where clauses out of multiple predicates. This topic explores predicate combinations and delves further into PredicateBuilder and PredicateDescription capabilities.
Two kinds of predicate A predicate is a function that returns true or false for an item it evaluates. A query Where clause takes a predicate that filters items to include in the query result. Predicates come in two forms in DevForce queries. an object of type Expression<Func<T, bool>>. This is referred to as a predicate expression. an object that implements the DevForce IdeaBlade.Core.IPredicateDescription interface such as IdeaBlade.Linq.PredicateDescription. You use the predicate expression form when defining a LINQ query and you know the type of object the predicate will evaluate. You use the IPredicateDescription form when you don't know the type of object to filter at compile time. Why do we need the PredicateBuider? We need PredicateBuilder because combining predicates of either kind is hard in .NET. Let's illustrate with two predicate expressions. C# Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A"); Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.Contains("e"); VB Dim expr1 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.StartsWith("A") Dim expr2 As Expression(Of Func(Of Customer, Boolean)) = _ Function(c As Customer) c.CompanyName.Contains("e") It's easy to use either of these expressions independently; it's not obvious how to combine them. C# var query1 = myEntityManager.Customers.Where(expr1); // ok var query2 = myEntityManager.Customers.Where(expr2); // ok var queryBoth = myEntityManager.Customers.Where(expr1 && expr2) // BAD - won't compile. VB Dim query1 = myEntityManager.Customers.Where(expr1) ' ok Dim query2 = myEntityManager.Customers.Where(expr2) ' ok Dim queryBoth = myEntityManager.Customers.Where(expr1 AndAlso expr2) ' BAD - won't compile. The PredicateBuilder can combine two predicates into a third predicate; then we pass that third predicate to the query's Where clause: C# var expr3 = PredicateBuilder.And(expr1, expr2); var queryBoth = myEntityManager.Customers.Where(expr3); // this works VB Dim expr3 = PredicateBuilder.And(expr1, expr2) Dim queryBoth = myEntityManager.Customers.Where(expr3) ' this works Here's a more convenient syntax that uses PredicateBuilder's predicate expression extension methods to do the same thing without mentioning PredicateBuilder explicitly: C# var expr3 = expr1.And(expr2); var queryBoth = myEntityManager.Customers.Where(expr3); // this works VB Dim expr3 = expr1.And(expr2) Dim queryBoth = myEntityManager.Customers.Where(expr3) ' this works PredicateBuilder APIs The PredicateBuilder is a static class with two kinds of static methods: 1. methods that take predicate expression parameters and return a predicate expression - the strongly typed API. 2. methods that take IPredicateDescription parameters and return an IPredicateDescription - the untyped API. The two APIs mirror each other. We'll describe the strongly typed API involving predicate expressions and assume confidently that you can extrapolate to the IPredicateDescription methods. Method Example And PredicateBuilder.And(p1, p2, p3 .. pn) Or PredicateBuilder.Or(p1, p2, p3 .. pn) Not PredicateBuilder.Not(p1) True PredicateBuilder.True<Product>() False PredicateBuilder.False<Product>() p = Predicate Expression, Expression<Func<T, bool>>, where 'T' is the type of the evaluated item. All combined predicates must have the same item type (e.g., Product). The True() and False() methods return predicate expression constants that simply help you jump-start your chaining of PredicateBuilder expressions ... as seen in the examples below. Additional overloads of Or(), And(), and Not() are extension methods that make fluent composition of predicates a little easier: p1.Or(p2) p1.And(p2) p1.Not() The parallel extension methods for IPredicateDescription are located in DynamicQueryExtensions rather than PredicateBuilder. PredicateBuilder examples Here are some examples using the PredicateBuilder predicate expression methods: C# Expression<Func<Product, bool>> p1, p2, p3, p4, bigP; // Sample predicate expressions p1 = p => p.ProductName.Contains("Sir"); p2 = p => p.ProductName.Contains("Cajun"); p3 = p => p.ProductName.Contains("Louisiana"); p4 = p => p.UnitPrice > 20; bigP = p1.Or(p2); // Name contains "Sir" or "Cajun" bigP = p1.Or(p2).Or(p3); // Name contains any of the three bigP = PredicateBuilder.Or(p1, p2, p3); // Name contains any of the 3 bigP = PredicateBuilder.Or(tests); // OR together some tests bigP = p1.And(p4); // "Sir" and price is greater than 20 // Name contains "Cajun" and "Lousiana" and the price is greater than 20 bigP = PredicateBuilder.And(p2, p3, p4); bigP = PredicateBuilder.And(tests); // AND together some tests // Name contains either Sir or Louisiana AND price is greater than 20 bigP = p1.Or(p3).And(p4); // bigP = PredicateBuilder.Not(p1); // Name does not contain "Sir" bigP = PredicateBuilder.True<Product>().And(p1);// same as p1 bigP = PredicateBuilder.False<Product>().Or(p1);// same as p1 // Not useful bigP = PredicateBuilder.True<Product>().Or(p1);// always true bigP = PredicateBuilder.False<Product>().And(p1);// always false VB Dim p1 As Expression(Of Func(Of Product, Boolean)), p2 As Expression(Of _ Func(Of Product, Boolean)), p3 As Expression(Of Func(Of Product, Boolean)), _ p4 As Expression(Of Func(Of Product, Boolean)), bigP As Expression(Of _ Func(Of Product, Boolean)) ' Sample predicate expressions p1 = Function(p) p.ProductName.Contains("Sir") p2 = Function(p) p.ProductName.Contains("Cajun") p3 = Function(p) p.ProductName.Contains("Louisiana") p4 = Function(p) p.UnitPrice > 20 bigP = p1.Or(p2) ' Name contains "Sir" or "Cajun" bigP = p1.Or(p2).Or(p3) ' Name contains any of the three bigP = PredicateBuilder.Or(p1, p2, p3) ' Name contains any of the 3 bigP = PredicateBuilder.Or(tests) ' OR together some tests bigP = p1.And(p4) ' "Sir" and price is greater than 20 ' Name contains "Cajun" and "Lousiana" and the price is greater than 20 bigP = PredicateBuilder.And(p2, p3, p4) bigP = PredicateBuilder.And(tests) ' AND together some tests ' Name contains either "Sir" or "Louisiana" AND price is greater than 20 bigP = p1.Or(p3).And(p4) bigP = PredicateBuilder.Not(p1) ' Name does not contain "Sir" bigP = PredicateBuilder.True(Of Product)().And(p1) ' same as p1 bigP = PredicateBuilder.False(Of Product)().Or(p1) ' same as p1 ' Not useful bigP = PredicateBuilder.True(Of Product)().Or(p1) ' always true bigP = PredicateBuilder.False(Of Product)().And(p1) ' always false Debugging PredicateBuilder methods Put a breakpoint on any of the "bigP" lines and ask the debugger to show you the result as a string. Here is the Immediate Window output for C# bigP = p1.Or(p3).And(p4); VB bigP = p1.Or(p3).And(p4) {p => ((p.ProductName.Contains("Sir") || p.ProductName.Contains("Louisiana")) && (p.UnitPrice > Convert(20)))} Lazy typed PredicateDescriptions We usually specify the item type when we create a PredicateDescription. That isn't strictly necessary. We can postpone determining the query type until the PredicateDescription is used in a query. For example: C# var p1 = new PredicateDescription("UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24); var p2 = new PredicateDescription("Discontinued", FilterOperator.IsEqualTo, true); var p3 = p1.And(p2); //unit price >= 24 and discontinued. VB Dim p1 = New PredicateDescription("UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24) Dim p2 = New PredicateDescription("Discontinued", FilterOperator.IsEqualTo, True) Dim p3 = p1.And(p2) ' unit price >= 24 and discontinued. The variables p1, p2, p3 are well-defined but do not have embedded query types. They can be used in any query for which they are appropriate, e.g., C# var productQuery = mgr.Products.Where(p3); var internationalProductQuery = mgr.InternationalProducts.Where(p3); // but not var customerQuery = mgr.Customers.Where(p3); VB Dim productQuery = mgr.Products.Where(p3) Dim internationalProductQuery = mgr.InternationalProducts.Where(p3) ' but not Dim customerQuery = mgr.Customers.Where(p3) The customerQuery compiles but throws at runtime because Customer doesn't have a UnitPrice or Discontinued property. The productQuery and internationalQuery will run because these properties are defined for Product and InternationalProduct. As a general rule it is best to embed the query type in the PredicateDescription when you define it unless you have a compelling reason to do otherwise. Filtering on anonymous types within a complex query is one such compelling reason. Convert a PredicateDescription to a "predicate expression" When a dynamic PredicateDescription has an embedded query type information, you can convert it into a strongly typed predicate expression using the ToLambdaExpression() method. Once converted, the expression is ready to use in a strongly-typed query, the kind of query you typically write in an application. The following code illustrates: C# // p1 was the first PredicateDescription that filtered for UnitPrice >= 24 var exprFunc = (Expression<Func<Product, bool>>)p1.ToLambdaExpression(); var filterQuery = anEntityManager.Products.Where(exprFunc); var results = anEntityManager.ExecuteQuery(filterQuery); VB ' p1 was the first PredicateDescription that filtered for UnitPrice >= 24 Dim exprFunc = CType(p3.ToLambdaExpression(), Expression(Of Func(_ Of Product, Boolean))) Dim filterQuery = _em1.Products.Where(exprFunc) Dim results = _em1.ExecuteQuery(filterQuery) This gambit is only possible when you can pass the PredicateDescription into compiled code that knows the target type ... as happened here. Such occasions are rare but they occur and the ToLambdaExpression() method is standing by when the opportunity to convert presents itself. Create dynamic "OrderBy" clauses Last modified on April 26, 2011 16:13 You can specify the "OrderBy" criteria dynamically with the I deaBlade.Linq.SortSelector when you can't determine the sort order at compile time. You create an instance of the SortSelector class by specifying the type of object to sort, the property to sort, and its sort direction. The OrderBySelector extension method makes it easy to use a SortSelector in a standard LINQ query you would otherwise have used OrderBy, OrderByDescending, ThenBy or ThenByDescending. For example: C# var sort = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); var customers = myEntityManager.Customers.OrderBySelector(sort).ToList();
// compare with the strongly-typed LINQ equivalent var customers = myEntityManager.Customers.OrderByDescending(c => c.Country).ToList(); VB Dim sort = New SortSelector(GetType(Customer), "Country", ListSortDirection.Descending) Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList()
' compare with the strongly-typed LINQ equivalent Dim customers = myEntityManager.Customers.OrderByDescending(Function(c) c.Country).ToList() A single SortSelector instance can specify multiple sort properties, each with its own sort direction. You add sort criteria with the ThenBy method. C# var sort = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); sort = sort.ThenBy("CompanyName"); // by default ListSortDirection is Ascending if not specified. var customers = myEntityManager.Customers.OrderBySelector(sort).ToList();
// compare with the strongly-typed LINQ equivalent var customers = myEntityManager.Customers .OrderByDescending(c => c.Country) .ThenBy(c => c.CompanyName) .ToList(); VB Dim sort = New SortSelector(GetType(Customer), "Country", ListSortDirection.Descending) sort = sort.ThenBy("CompanyName") ' by default ListSortDirection is Ascending if not specified. Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList()
' compare with the strongly-typed LINQ equivalent Dim customers = myEntityManager.Customers .OrderByDescending(Function(c) c.Country) .ThenBy(Function(c) c.CompanyName) .ToList() SortSelectors can also be combined with an overload of the ThenBy method or with the static Combine method. C# // create two SortSelectors var sort1 = new SortSelector(typeof(Customer), "Country", ListSortDirection.Descending); var sort2 = new SortSelector(typeof(Customer), "CompanyName");
// combine them using either of the following two mechanisms. var sort = sort1.ThenBy(sort2); // or var sort = SortSelector.Combine(new[] { sort1, sort2 }); VB ' create two PropertySelectors Dim sort1 = New SortSelector(GetType(Customer), _ "Country", ListSortDirection.Descending) Dim sort2 = New SortSelector(GetType(Customer), "CompanyName")
' combine them using either of the following two mechanisms. Dim sort = sort1.ThenBy(sort2) ' or Dim sort = SortSelector.Combine( { sort1, sort2 })
The syntax and behavior of a SortSelector are similar to the PredicateDescription used in a Where clause as described here and here. For example, you can delay specifying the type of entity to sort until you actually use the SortSelector. C# // Type not specified; it is determined when used var sort = new SortSelector("Country", ListSortDirection.Descending); var customers = myEntityManager.Customers.OrderBySelector(sort).ToList(); VB ' Type not specified; it is determined when used Dim sort = New SortSelector("Country", ListSortDirection.Descending) Dim customers = myEntityManager.Customers.OrderBySelector(sort).ToList() Such lazy-typing can be useful when sorting anonymous types. PropertySortSelector is deprecated The IdeaBlade.Linq.PropertySortSelector was the less-capable class for ordering query result dynamically in versions of DevForce prior to v.6.1.0. That class has been deprecated in favor of SortSelector and will be removed from the product at a future date. Create dynamic "Select", "SelectMany" and "GroupBy" clauses You can construct Select, SelectMany and GroupBy clauses dynamically with the IdeaBlade.Linq.ProjectionSelector when you can't specify them at compile time.
The LINQ Select, SelectMany and GroupBy clauses change the shape of a query result. Queries that uses these clauses are called "projections" and we refer to them as "projection clauses" in the following discussion. When you need to construct a projection query but you don't know the types involved at compile time, you can create an instance of the ProjectionSelector class and pass in the type information at runtime. Select examples
When creating a ProjectionSelector class you specify the properties to select (AKA, "project"). You can also specify the type of object to which the properties belong ... if you know the type ... or delay type identification until you use the selector. Consider a simple example in which we query for Customers and return ("project") their CompanyNames. If we knew we were going to do this at compile time, we'd write: C# var companyNames = anEntityManager.Customers.Select(c => c.CompanyName).ToList(); VB Dim companyNames = anEntityManager.Customers.Select(Function(c) c.CompanyName).ToList() But we don't know. So we write a general purpose function, DoSelect, that is capable of projecting an arbitrary property belonging to an arbitrary entity type. To see it in action, we call it with the Customer type and specify the CompanyName type as before. C# public IEnumerable DoSelect(EntityManager manager, Type entityType, string propertyName) { var selector = new ProjectionSelector(propertyName);
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery.Select(selector); // selector learns its type from rootQuery var results = query.Execute(); // synchronous query execution return results; }
// The company names of every Customer var companyNames = DoSelect(anEntityManager, typeof(Customer), "CompanyName"); VB Public Function DoSelect(ByVal manager As EntityManager, ByVal entityType As Type, ByVal propertyName As String) As IEnumerable
Dim selector = New ProjectionSelector(propertyName)
Dim rootQuery = EntityQuery.Create(entityType, manager) Dim query = rootQuery.Select(selector) ' selector learns its type from rootQuery Dim results = query.Execute() ' synchronous query execution Return results End Function
' The company names of every Customer Dim companyNames = DoSelect(anEntityManager, GetType(Customer), "CompanyName") Any property of a class can be projected by passing in its property name, or as in the next example, passing in a nested property name. C# // The company names of every Order's Customer var companyNames = DoSelect(anEntityManager, typeof(Order), "Customer.CompanyName"); VB ' The company names of every Order's Customer Dim companyNames = DoSelect(anEntityManager, GetType(Order), "Customer.CompanyName") Select into a dynamic type We can use the Select clause to project multiple values into a single instance of a DevForce dynamic type (we explain dynamic types below). In our next example, we get a few Products, find their related Category entities, and project two of the Category properties into a dynamic type. For ease of exposition, we specify Product, Category and its properties in the code. If you actually knew the types and properties at design time you'd use strongly typed LINQ statements and wouldn't bother with the ProjectionSelector. We trust you appreciate our true intention. C# IProjectionSelector selector = new ProjectionSelector("Category.Name", "CatName"); selector = selector.Combine("Category.Description", "CatDesc");
var rootQuery = EntityQuery.Create(typeof(Product), anEntityManager); var query = rootQuery.Select(selector); var results = query.Execute(); VB Dim selector = New ProjectionSelector("Category.Name", "CatName") as IProjectionSelector selector = selector.Combine("Category.Description", "CatDesc"))
Dim rootQuery = EntityQuery.Create(GetType(Product), anEntityManager) Dim query = rootQuery.Select(selector) Dim results = query.Execute() A perhaps more graceful syntax, especially when there are many properties, might be: C# var selector = new AnonymousProjectionSelector() .Combine("Category.Name", "CatName") .Combine("Category.Description", "CatDesc");
var rootQuery = EntityQuery.Create(typeof(Product), anEntityManager); var query = rootQuery.Select(selector); var results = query.Execute(); VB Dim selector = New AnonymousProjectionSelector() .Combine("Category.Name", "CatName") .Combine("Category.Description", "CatDesc")
Dim rootQuery = EntityQuery.Create(typeof(Product), anEntityManager) var query = rootQuery.Select(selector) Dim results = query.Execute()
This form draws proper attention to the AnonymousProjectionSelector and the use of the Combine extension method to build up the properties of the projection. The "CatName" and "CatDesc" arguments are the aliases for the projected nested properties; they become the names of the two properties of the DevForce dynamic type objects returned in the results. Finally, a quite general projection function which suggests the potential for this approach: C# public IEnumerable DoSelect( EntityManager manager, Type entityType, params string[] propertyNames) { var selector = new AnonymousProjectionSelector(); foreach (var name in propertyNames) { var alias = name.Replace(".", "_"); selector = selector.Combine(name, alias); }
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery.Select(selector); // selector learns its type from rootQuery var results = query.Execute(); // synchronous query execution return results; } VB Public Function DoSelect(ByVal manager As EntityManager, _ ByVal entityType As Type, ByVal ParamArray propertyNames As String()) As IEnumerable
Dim selector = new AnonymousProjectionSelector() For Each name As String In propertyNames Dim alias = name.Replace(".", "_") selector = selector.Combine(name, alias) Next
Dim rootQuery = EntityQuery.Create(entityType, manager) Dim query = rootQuery.Select(selector) ' selector learns its type from rootQuery Dim results = query.Execute() ' synchronous query execution Return results
End Function
The DevForce dynamic type The DevForce dynamic type returned in query results may be treated exactly like a standard .NET anonymous type. DevForce cannot return an actual .NET anonymous type because the compiler insists that anonymous types be known at compile time. The dynamic types DevForce creates are not defined until runtime. Therefore, DevForce dynamically creates a type that has the same semantics as an anonymous type by emitting IL at runtime. A DevForce dynamic type has one additional benefit: it is a public class which means that Silverlight controls can bind to it. Silverlight controls can't bind to .NET anonymous classes becauses they are declared internal. You access properties of a dynamic type much as you would properties of a .NET anonymous type as we see in the following two examples. The first makes use of the dynamic keyword introduced in .NET 4. C# foreach (dynamic item in results) { String categoryName = (String) item.CatName; String categoryDescription = (String)item.CatDesc; } VB For Each item As dynamic In results Dim categoryName As String = CType(item.CatName, String) Dim categoryDescription As String = CType(item.CatDesc, String) Next item The second uses the DevForce IdeaBlade.Core.AnonymousFns helper class to deconstruct a dynamic type into an object array. C# var elementType = results.Cast<Object>().FirstOrDefault().GetType(); var items = AnonymousFns.DeconstructMany(results, false, elementType);
foreach (var item in items) { String categoryName = (String) item[0]; String categoryDescription = (String)item[1]; } VB Dim elementType = results.Cast(Of Object)().FirstOrDefault().GetType() Dim items = AnonymousFns.DeconstructMany(results, False, elementType)
For Each item In items Dim categoryName As String = CType(item(0), String) Dim categoryDescription As String = CType(item(1), String) Next item The memory footprint of dynamic types Dynamic projections are convenient but should be used with care. Every .NET type definition consumes memory that cannot be reclaimed by the .NET garbage collector. This is as true of the dynamic types that DevForce creates as it is of .NET anonymous types. Queries that return the same dynamic type "shape" - the same properties, of the same types, in the same order, with the same names - are not a problem; dynamic type definitions are cached and a type with matching shape is reused. But it's possible for client applications to create an endless variety of dynamic type shapes. While the amount of memory consumed by a single type is tiny and almost never an issue on a single client machine, each of these types is also created on the EntityServer where the query is executed. A long-running EntityServer might accumulate large numbers of distinct dynamic type definitions. The server could run out of memory if it saw millions of different anonymous types in which case you'll have to recycle the server periodically if this becomes a problem. It's not a great risk but we felt we should mention it. SelectMany example A dynamic implementation of the LINQ SelectMany operation also makes use of the ProjectionSelector. The following contrived example shows how to use SelectMany to get the OrderDetails associated with the first five Products in the database. It relies upon a peculiar function, DoSelectMany, that gets an arbitrary set of related entities from the first five instance of some kind of entity. C# public IEnumerable DoSelectMany( EntityManager manager, Type entityType, string propertyName) { var selector = new ProjectionSelector(propertyName);
var rootQuery = EntityQuery.Create(entityType, manager); var query = rootQuery .Take(5) // reduce the result size for exposition. .SelectMany(selector); var results = query.Execute(); return results; }
var orderDetails = DoSelectMany(anEntityManager, typeof(Product), "OrderDetails") .Cast<OrderDetail>() // convert IEnumerable to IEnumerable<OrderDetails> .ToList(); VB Public Function DoSelectMany(ByVal manager As EntityManager, _ ByVal entityType As Type, ByVal propertyName As string) As IEnumerable
var selector = New ProjectionSelector(propertyName)
Dim rootQuery = EntityQuery.Create(entityType, mManager) Dim query = rootQuery .Take(2) ' reduce the result size for exposition. .SelectMany(selector) Dim results = query.Execute() Return results
End Function
Dim orderDetails = DoSelectMany(anEntityManager, GetType(Product), "OrderDetails") .Cast<OrderDetail>() ' convert IEnumerable to IEnumerable<OrderDetails> .ToList() GroupBy example
A dynamic implementation of the LINQ GroupBy operation also makes use of the ProjectionSelector. In the following example, the SimpleGroupBy function queries for every instance of a given type and groups the results by one of the properties of that type. C# public IEnumerable SimpleGroupBy(EntityManager manager, Type entityType, string groupByProperty) { var groupBy = new ProjectionSelector(groupByProperty, alias);
var query = EntityQuery.Create(entityType, manager) .GroupBy(groupBy); var result = manager.ExecuteQuery(query); return result; }
// Static equivalent: anEntityManager.Products.GroupBy(_ => _.Category.CategoryName); var result = SimpleGroupBy(anEntityManager, typeof(Product), "Category.CategoryName");
var productGrps = result.Cast<IGrouping<String, Product>>().ToList(); VB Public Function SimpleGroupBy(ByVal manager As EntityManager, ByVal entityType As Type,ByVal groupByProperty As String) _ As IEnumerable
Dim groupBy = new ProjectionSelector(groupByProperty, alias)
Dim query = EntityQuery.Create(entityType, manager) .GroupBy(groupBy) Dim result = manager.ExecuteQuery(query) Return result End Function
' Static equivalent: anEntityManager.Products.GroupBy(Function(_) _.Category.CategoryName) Dim result = SimpleGroupBy(anEntityManager, GetType(Product), "Category.CategoryName")
Dim productGrps = result.Cast(Of IGrouping(Of String, Product))().ToList()
PropertyProjectionSelector is deprecated The IdeaBlade.Linq.PropertyProjectionSelector is now obsolete, having been superseded by the ProjectionSelector. It will be removed from the product at a future date.
Requery Last modified on April 21, 2011 19:08 Contents RefetchEntity/RefetchEntities methods Fetch and Re-fetch of navigation properties There are several ways of forcing the requery of previously cached data. This can important when you want to insure that the local entity cache has the absolutely latest data. One approach is to use one of the RefetchEntities overloads on the EntityManager:
RefetchEntity/RefetchEntities methods C# public void RefetchEntity(Object entity, MergeStrategy mergeStrategy); public void RefetchEntities(IEnumerable entities, MergeStrategy mergeStrategy); public void RefetchEntities(EntityKeyList entityKeys, MergeStrategy mergeStrategy); public void RefetchEntities(EntityState entityState, MergeStrategy mergeStrategy); VB Public Sub RefetchEntity(ByVal entity As Object, ByVal mergeStrategy As MergeStrategy) Public Sub RefetchEntities(ByVal entities As IEnumerable, ByVal mergeStrategy As MergeStrategy) Public Sub RefetchEntities(ByVal entityKeys As EntityKeyList, ByVal mergeStrategy As MergeStrategy) Public Sub RefetchEntities(ByVal entityState As EntityState, ByVal mergeStrategy As MergeStrategy) There are also asynchronous versions of each of the above. One warning about these methods; they are all designed to work with reasonably small numbers of entities ( < 1000). While they will work with larger numbers of entities; performance may be poor. The reason for this is that these methods are implemented so that they in effect create a large "IN" or "OR" query for all of the desired entities by key. The query expression itself can therefore become very large for large numbers of entities. Expressions that are this large will have performance impacts in both serialization as well as query compilation. For those cases where very large numbers of entities need to be refreshed, it is usually a better idea to write a "covering" query that is much smaller textually but returns approximately the same results. You may find that even though you return more entities than are needed with this covering query, the resulting overall performance is still better. The MergeStrategy determines how the refetched data will be merged into cache. 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 OverwriteChanges Overwrites the cached entity with incoming data and uses the EntityState of the incoming entity PreserveChanges Replace unchanged entities but keep changed entities as they are. PreserveChangesUnlessOriginalObsolete Preserves the persistent state of the cached entity if the entity is current. Overwrites an entity if it is obsolete and gives it the EntityState of the incoming entity. PreserveChangesUpdateOriginal Preserves the persistent state of the cached entity whether it is current or not. Overwrites the Original version of the entity if obsolete. How is Current determined? DevForce uses the ConcurrencyProperties youve defined for the Entity. If the values of the concurrency properties are the same between the original and incoming entity, then the entity is considered current. If you havent defined a concurrency property for the entity then DevForce has no way of determining currency correctly, so assumes that the entity is current. What do we mean by the Original version? Every entity can have up to three versions of property values. All entities will have current values; modified entities will also have original values, i.e., the property values before the entity was modified. Entities in edit mode will also have proposed values, i.e., the values changed during the edit but not yet committed. Merge strategies are described in further detail later in this document.
Fetch and Re-fetch of navigation properties You can also control how navigation properties are loaded, or force a re-load, using property metadata for these properties called the NavigationEntityProperty. Remember that every generated entity in the domain model has a nested PropertyMetadata class giving you access to all EntityProperty definitions for that type. You can also obtain this information from the EntityMetadataStore. With the NavigationEntityProperty for a particular property in hand, you can then obtain the EntityReference, which gives you low-level access to the property and its status. You obtain the EntityReference from the NavigationProperty itself. For example, C# Customer aCustomer = _em1.Customers.First(); var navProp = Customer.PropertyMetadata.OrderSummaries; var entityRef = navProp.GetEntityReference(aCustomer); VB Dim aCustomer As Customer = _em1.Customers.First() Dim navProp = Customer.PropertyMetadata.OrderSummaries Dim entityRef = navProp.GetEntityReference(aCustomer) Once you have the EntityReference, a call to Load(MergeStrategy) will force a re-fetch of the related entities, and use the specified MergeStrategy to merge the data into cache. The MergeStrategy here functions exactly the same as when used with the EntityManager RefetchEntities methods described above. C# Customer aCustomer = _em1.Customers.First(); Customer.PropertyMetadata.OrderSummaries.GetEntityReference( aCustomer).Load(MergeStrategy.PreserveChangesUpdateOriginal); VB Dim aCustomer As Customer = _em1.Customers.First() Customer.PropertyMetadata.OrderSummaries.GetEntityReference(aCustomer) _ .Load(MergeStrategy.PreserveChangesUpdateOriginal) By default, all navigation properties are lazily loaded. To change this load strategy you can do so easily using the ReferenceStrategy defined for every NavigationEntityProperty. For example, C# var ers = new EntityReferenceStrategy( EntityReferenceLoadStrategy.Load, MergeStrategy.PreserveChanges); var navProp = Customer.PropertyMetadata.OrderSummaries; navProp.ReferenceStrategy = ers; VB Dim ers = New EntityReferenceStrategy(EntityReferenceLoadStrategy.Load, _ MergeStrategy.PreserveChanges) Dim navProp = Customer.PropertyMetadata.OrderSummaries navProp.ReferenceStrategy = ers The EntityReferenceLoadStrategy is an enum which allows you to choose among "Lazy", "DoNotLoad", and "Load" options. The default, Lazy indicates that the related data will be loaded once when first accessed. Use "DoNotLoad" if the related data should not ever be retrieved from the back end data store; and use "Load" to force a retrieval from the back end data store upon every access.
Useful query extension methods Last modified on April 21, 2011 19:08 Contents The With() Extension Method The FirstOrNullEntity() ExtensionMethod The ToQuery () ExtensionMethod DevForce provides several additional "query support" extension methods.. All of these methods are defined as extensions to IEntityQuery. The With() Extension Method The With() extension method permits you to substitute a different QueryStrategy, a different target EntityManager, or both, on any existing instance of an IEntityQuery. The original query will be left unaltered. When a call to With() is chained to a query, the result may be either a new query or a reference to the original query. Normally it will be a new query, but if the content of the With() call is such that the resultant query would be the same as the original one, a reference to the original query is returned instead of a new query. If you ever want to be sure that you get a new query, use the Clone() extension method instead of With(). With() avoids the overhead of a Clone() when a copy is unnecessary. You can pass null arguments to With(). When a query has a null EntityManager assigned, it uses the DefaultManager. When a query has a null QueryStrategy, it uses the DefaultQueryStrategy of the assigned (or default) EntityManager. See the code below for more detail on the possibilities. C# // Setup DomainModelEntityManager em1 = null; DomainModelEntityManager em2 = null;
//Use With() to run the existing query against a different EntityManager: List<Customer> customers = new List<Customer>(query0.With(em2));
//The next two examples use With() to run the query with a different QueryStrategy. //The With() call in the right-hand side of the following statement //specifies a query that is materially different from query0, in //that it has a different QueryStrategy associated with it. //Accordingly, the right-hand side of the statement will return //a new query: IEntityQuery<Customer> query1 = query0.With(QueryStrategy.CacheOnly);
//Because the content of the With() call in the right-hand side //of the following statement doesn't result in a modification //of query0, the right-hand side will return a reference to //query0 rather than a new query. IEntityQuery<Customer> query2 = query0.With(QueryStrategy.DataSourceOnly);
//If you want to be certain you get a new query, use Clone() //rather than With(): EntityQuery<Customer> query3 = (EntityQuery<Customer>)query0.Clone(); query3.QueryStrategy = QueryStrategy.DataSourceOnly;
//Change both the QueryStrategy and the EntityManager IEntityQuery<Customer> query4 = query0.With(em2, QueryStrategy.CacheOnly);
//You can pass null arguments to With(). When a query has a null EntityManager, //assigned, it uses the DefaultManager. When a query has a null QueryStrategy, //it uses the DefaultQueryStrategy of the assigned (or default) EntityManager. //Run the query against the default EntityManager, using its default QueryStrategy: IEntityQuery<Customer> query5 = query0.With(null, null);
//When you pass a single null to With, you must cast it to the appropriate //type so the compiler know's which single-parameter overload you mean to use: //Run the query against the default EntityManager, using the base query's //assigned QueryStrategy: IEntityQuery<Customer> query6 = query0.With((DomainModelEntityManager)null);
//Run the query against the assigned EntityManager, using that EntityManager's //default QueryStrategy: IEntityQuery<Customer> query7 = query0.With((QueryStrategy)null); VB ' Setup Dim em1 As DomainModelEntityManager = Nothing Dim em2 As DomainModelEntityManager = Nothing
Dim query0 As IEntityQuery(Of Customer) = em1.Customers.Where( _ Function(c) c.CompanyName.ToLower().StartsWith("a")). _ With(QueryStrategy.DataSourceOnly)
'Use With() to run the existing query against a different EntityManager: Dim customers As New List(Of Customer)(query0.With(em2))
'The next two examples use With() to run the query 'with a different QueryStrategy. 'The With() call in the right-hand side of the following statement 'specifies a query that is materially different from query0, in 'that it has a different QueryStrategy associated with it. 'Accordingly, the right-hand side of the statement will return 'a new query: Dim query1 As IEntityQuery(Of Customer) = _ query0.With(QueryStrategy.CacheOnly)
'Because the content of the With() call in the right-hand side 'of the following statement doesn't result in a modification 'of query0, the right-hand side will return a reference to 'query0 rather than a new query. Dim query2 As IEntityQuery(Of Customer) = _ query0.With(QueryStrategy.DataSourceOnly)
'If you want to be certain you get a new query, use Clone() 'rather than With(): Dim query3 As EntityQuery(Of Customer) = CType(query0.Clone(), _ EntityQuery(Of Customer)) query3.QueryStrategy = QueryStrategy.DataSourceOnly
'Change both the QueryStrategy and the EntityManager Dim query4 As IEntityQuery(Of Customer) = query0.With(em2, _ QueryStrategy.CacheOnly)
'You can pass null arguments to With(). When a query has a null 'EntityManager assigned, it uses the DefaultManager. When a query has 'a null QueryStrategy, it uses the DefaultQueryStrategy of the 'assigned (or default) EntityManager. 'Run the query against the default EntityManager, 'using its default QueryStrategy: Dim query5 As IEntityQuery(Of Customer) = _ query0.With(Nothing, Nothing)
'When you pass a single null to With, you must cast it to the appropriate 'type so the compiler know's which single-parameter overload you mean to use: 'Run the query against the default EntityManager, using the base query's 'assigned QueryStrategy: Dim query6 As IEntityQuery(Of Customer) = _ query0.With(CType(Nothing, DomainModelEntityManager))
'Run the query against the assigned EntityManager, using that 'EntityManager's default QueryStrategy: Dim query7 As IEntityQuery(Of Customer) = _ query0.With(CType(Nothing, QueryStrategy)) The FirstOrNullEntity() ExtensionMethod LINQ to Entities provides First() and FirstOrDefault() extension methods on queries. First() returns the first item in a collection meeting the query criteria; FirstOrDefault() returns that, or if no items meet the criteria, the default value for the target type. For integer target types, FirstOrDefault() returns a zero; for string types, it returns an empty string. For complex types or other types that have no default, it returns a null. DevForce adds a FirstOrNullEntity extension method that can be used when you are querying for target types that inherit from IdeaBlade.EntityModel.Entity. If no entity meets the specified criteria, FirstOrNullEntity() returns the DevForce NullEntity" for the target type. The NullEntity is a non-saveable, immutable, syntactically correct instance of an entity represents "nothing there" but will not trigger an exception.
The ToQuery () ExtensionMethod Every IdeaBlade.EntityModel.Entity has a ToQuery() extension method that returns an IEntityQuery<T> where T is an Entity type. This IEntityQuery<T> specifies the Entity on which it was based using its EntityAspect.EntityKey, and can be extended to perform various useful operations. Consider, for example, the following statements: C# Customer aCustomer = _em1.Customers.FirstOrNullEntity(); var query = aCustomer.ToQuery<Customer>() .Include(Customer.PathFor(c => c.Orders)); query.With(QueryStrategy.DataSourceOnly).ToList(); VB Dim aCustomer As Customer = _em1.Customers.FirstOrNullEntity() Dim query = aCustomer.ToQuery().Include(Customer.PathFor(Function(c) c.Orders)) query.With(QueryStrategy.DataSourceOnly).ToList() Here, from a Customer entity, we have created a query that will retrieve that same Customer. We have then extended with a call to Include() it to create a span query that will also retrieve all of that Customers associated Orders. We do not otherwise have so convenient a way to accomplish this goal. The ToQuery() extension method is also provided on any IEnumerable<T> collection, when T is an Entity. Thus you can turn an arbitrary list of Customers into a query that will return the same set of Customers. The Where() clause on the resultant query will specify a series of ORd key values. For example, consider the following statements: C# List<Customer> customers = _em1.Customers .Where(c => c.CompanyName.ToLower().StartsWith("a")).ToList(); var query2 = customers.ToQuery<Customer>(); VB Dim customers As List(Of Customer) = _em1.Customers _ .Where(Function(c) c.CompanyName.ToLower().StartsWith("a")).ToList() Dim query2 = customers.ToQuery() Placing query2 in a watch window reports its value as the following: {value(IdeaBlade.EntityModel.EntityQueryProxy`1[DomainModel.Customer]).Where(t => ((((t.CustomerID = 785efa04-cbf2-4dd7-a7de-083ee17b6ad2) || (t.CustomerID = b61cf396- 206f-41a6-9766-168b5cbb8edd)) || (t.CustomerID = f214f516-d55d-4f98-a56d- 7ed65fd79520)) || (t.CustomerID = 256d4372-baa7-4937-9d87-d9a4e06146f8)))} The first query evidently placed four Customers in the customers list; the query returned by ToQuery() specifies those four by their (GUID) key values. Add a named query Write a named query method on the server when you want explicit control over how the server interprets a LINQ query received from the client.
Named queries make it easy to dictate the server-side implementation of a query. You can write several named queries for a particular entity type which means you can offer clients a menu of query alternatives that each express a distinctive intent. A named query is a convenient way to consolidate in a single method the logic to: authorize the query modify the query, perhaps by adding filters or limiting the amount of data returned add Include clauses that package related entities in the query result Named queries are methods that return an IQueryable or an IEnumerable of an entity type. The type can be any serializable class produced by almost any means imaginable. Most named queries will involve Entity Framework entity types produced by an Entity Framework query. There are two basic kinds of named query: the default named query and specialized named queries. Named queries were introduced as a beta feature in DevForce version 6.1.0.0 The default named query The default named query is a named query that represents all entities of a particular type in the database. The signature of the default named query for the Customer entity type might be as follows: C# public IQueryable<Customer> GetCustomers(); VB public IQueryable(Of Customer) GetCustomers() Various GetCustomers implementations are considered in the "default named query" topic. For the moment it's worth noting that the method returns an IQueryable (or IEnumerable) of a specific type there are no parameters the method name consists of a "Get" prefix followed by an EntitySet name, usually the plural form of the entity type name; you'll learn how to name a query method elsewhere. When the EntityServer receives a Customer client query that corresponds to this named query, it builds a new query by copying the client query LINQ clauses and adding them to the output of the named query method. All named queries are composable in this way. For example, DevForce translates a client query for Customers beginning with the letter 'B' such as this one: C# myEntityManager.Customers.Where(c => c.StartsWith("B"); VB myEntityManager.Customers.Where(Function(c) c.StartsWith("B") into something like: C# GetCustomers().Where(c => c.StartsWith("B"); VB GetCustomers().Where(Function(c) c.StartsWith("B") The EntityServer Default named queries are optional You do not have to write a default named query method. When the DevForce EntityServer receives a client query and there is no default named query method on the server, DevForce processes it directly using its own default get-all-entities query as the root. If you decide to write a default named query, you can write only one per entity type. Specialized named queries The developer may wish to write one or more specialized named query methods for a particular entity type. Each method is associated with a particular business purpose and, accordingly, returns a different set of entities. For example, you might have one specialized named query for Gold Customers and another specialized named query returning the current users Western Region Customers. Specialized named query methods can take parameters, unlike the default named query which must be parameterless. The EntityServer must be able to find the specialized named query method if the client query asks for it. Write a class for the named query methods Named queries are instance methods of a class. DevForce can find them if the class resides in a discoverable assembly. There isnt much special about that class and you can have as many such classes as you like. There are only a few requirements. 1. it must be public 2. it must have a default constructor (or no constructor) 3. it must be adorned with the EnableClientAccess attribute. 4. it must reside in an assembly that the DevForce EntityServer will discover. Please keep your named query provider classes stateless if possible. If it must have state, give great care to ensuring safe concurrent access to that state. Please avoid putting anything in this class other than what is strictly necessary to achieve its purpose. The named query provider class is a poor choice for a grab-bag of server-side features. Here is an example of a provider class. C#
[EnableClientAccess] public class NamedQueryServiceProvider { public IQueryable<Customer> GetCustomers() {...} } VB <EnableClientAccess> Public Class NamedQueryServiceProvider Public Function GetCustomers() As IQueryable(Of Customer) ...} End Function You must put this NamedQueryServiceProvider class in an assembly deployed on the server. The domain model project would do; the domain model assembly is always discoverable and deployed to the server. If you do not want the named query methods exposed on the client, add it to a separate, full .NET class library project that you only deploy to the server. Assembly discovery is discussed here. Named query security We trust you are authenticating clients before you accept their queries. Even so, you may be uncomfortable simply executing every query a client sends you, even if the client has been authenticated. You may prefer to inspect, modify, and possibly reject a query. The easiest approach is to add query security attributes to the method definitions. The DevForce EntityServerQueryInterceptor is affords the most complete and custom control over the query and its interpretation. You can inherit from our base version of that class and override one or more of its virtual methods. The DevForce EntityServer calls these methods as it processes the query. Your overrides can terminate the query, replace or modify the query, or even alter the query results. Such an interceptor remains the most powerful query management mechanism in DevForce. All queries named and unnamed pass through the interceptor. Anything you can do in a named query you can do in the interceptor. Many developers will combine named queries and query interception for a balance of convenience and power. Learn more Other subordinate topic pages explain in greater detail how to write named queries and use them appropriately.
Add the default named query The default named query is a server-side query method that represents the entire EntitySet for a given entity type. In this topic we discuss what a default named query is and how to write one.
The default named query is a server-side query method that represents the entire EntitySet for a given entity type. For example, the EntitySet for the Customer entity type is called Customers. When a client submits a query that is rooted in the Customers EntitySet and youve written a named query method for that EntitySet, such as GetCustomers, then that method is the default named query. Conceptually the default named query means return to the client every instance of this type. You write this method on the server. Whether it in fact returns every instance of the type is up to you. The following discussion begins with a client query for Customers when there is no named query. It identifies the root query to which you add filters, ordering, grouping, and selection. Then it shows two examples of a default named query that DevForce EntityServer substitutes for the root query when processing a query from the client. A separate topic explains how to write a specialized named queries that can address a narrower business purpose. A simple client query Here is a query that you might write on the client to retrieve all customers in the database. C# query = myEntityManager.Customers; // uses the generated query property VB query = myEntityManager.Customers ' uses the generated query property Customers is a factory property that returns an EntityQuery for Customer entities. The DevForce code generator added this property to the models custom EntityManager for your convenience. When you examine the generated code file, youll see that it was implemented as follows: C# query = new EntityQuery<Customer>("Customers", myEntityManager); VB query = New EntityQuery(Of Customer)("Customers", myEntityManager)
Locate the EntityQueries region of the generated entity class file. The string, Customers, is the name of the EntitySet associated with the Customer entity type. DevForce interprets this to mean the unrestricted set of all customers. The query is also associated with a particular EntityManager, the one in the myEntityManager variable. That means the query can be executed directly by writing something like this C# query.ExecuteAsync(queryCallBack); // get the results in the callback VB query.ExecuteAsync(queryCallBack) ' get the results in the callback We dont have to specify the EntityManager when we create the query. We might want to re- use the query with different EntityManager instances in which case we can write. C# query = new EntityQuery<Customer>("Customers"); em1.ExecuteQueryAsync(query, queryCallback); em2.ExecuteQueryAsync(query, queryCallback); VB query = New EntityQuery(Of Customer)("Customers") em1.ExecuteQueryAsync(query, queryCallback) em2.ExecuteQueryAsync(query, queryCallback) Query Root The client query examples weve seen so far return all customers in the database. Of course you can restrict the query further by adding Where clauses such as one that returns only customers beginning with the letter B. C# query-B-Customers = new EntityQuery<Customer>("Customers") .Where(c => c.StartsWith("B"); myEntityManager.ExecuteQueryAsync(query-B-Customers, queryCallback); VB query-B-Customers = New EntityQuery(Of Customer)("Customers").Where(Function(c) c.StartsWith("B") myEntityManager.ExecuteQueryAsync(query-B-Customers, queryCallback) Both the original query and this filtered query begin with the same root, the EntityQuery("Customers"). This is the root of the query no matter what LINQ clauses are added, no matter what types are ultimately returned. Customers is the name of the EntitySet that includes all Customer entities in the database. It defines the default query.
Keep your eye on the root and the EntitySet name. If you create a specialized named query (as described elsewhere), you will create a different root that uses a different EntitySet name. Add a do-nothing named query Now lets write a default named query method in a named query provider class. C# public IQueryable<Customer> GetCustomers() { return new EntityQuery<Customer>(); VB Public Function GetCustomers() As IQueryable(Of Customer) Return New EntityQuery(Of Customer)() End Function } Named queries are methods that return an IQueryable or an IEnumerable of an entity type. GetCustomers returns an IQueryable of Customer. The definition of this query method should seem familiar. The returned value is almost exactly the same as the EntityQuery we wrote on the client. Its only missing the EntitySet string (Customers). Thats OK because DevForce infers the Customers entity set name automatically. It also lacks an EntityManager. That is to be expected on the server; the query will be used by a different EntityManager instance every time. DevForce can tell that this is a query method for the Customer type because it has the right signature. It has no parameters and returns an IQueryable of Customer. DevForce also determines that this is the default named query: the named query to use when the client sends a query specifying the Customers EntitySet. The EntityServer found this method by applying the DevForce query naming conventions during its search. It stripped off the Get from the method name, leaving the word Customers which matches the name of the Customer types EntitySet. You can use the Query Merging the named query with the original client query Once DevForce finds the named query that matches the clients query, it merges the two queries by copying the LINQ clauses of the client query to the output of the named query method. You can imagine DevForce doing something like the following with query-B- Customers: C# // query-B-Customers = new EntityQuery<Customer>().Where(c => c.StartsWith("B"); mergedQuery = GetCustomers().Where(c => c.StartsWith("B"); VB ' query-B-Customers = new EntityQuery<Customer>().Where(c => c.StartsWith("B"); mergedQuery = GetCustomers().Where(Function(c) c.StartsWith("B") Then the EntityServer forwards the mergedQuery to the DevForce EntityServerQueryInterceptor or to your custom EntityServerQueryInterceptor Add a do-something named query The GetCustomers method we just wrote does exactly what DevForce would do anyway. Theres no point in writing a default named query unless it adds value. You do not have to write a default named query. But we can write a GetCustomers method that adds value such as this one: C# [RequiresRoles("admin")] public IQueryable<Customer> GetCustomers() { return new EntityQuery<Customer>().Include("Orders"); } VB <RequiresRoles("admin")> Public Function GetCustomers() As IQueryable(Of Customer) Return New EntityQuery(Of Customer)().Include("Orders") End Function Now only administrators can issue a query for Customers, thanks to the RequiresRoles attribute . The query also automatically includes the Order entities related to the Customers returned by the query. The result returned to the client will be further restricted to Customers whose names begin with B because that criterion was copied over from the client's query-B-Customers query.
Lets try a different implementation: C# public IQueryable<Customer> GetCustomers() { var user = System.Threading.Thread.CurrentPrincipal as User; EnsureValidUser(user); // only show the users own Customers return new EntityQuery<Customer>() .Where(c => c.UserID = user.ID); } VB Public Function GetCustomers() As IQueryable(Of Customer) Dim user = TryCast(System.Threading.Thread.CurrentPrincipal, User) EnsureValidUser(user) ' only show the users own Customers Return New EntityQuery(Of Customer)().Where(Function(c) c.UserID = user.ID) End Function In this one, the named query extracts the user from the server IPrincipal and limits the query to Customers that belong to the client user only. The EnsureValidUser method (not shown) throws a useful exception if the IPrincipal is not a proper User object. After merging with the client query-B-Customers, the query results contain only those Customers whose names begin with B and The client query does not change You can add, modify, or delete the default named query without changing your client query. Client query syntax remains the same and the query is used the same way, with or without a default named query on the server. The presence, absence, or nature of the default named query is structurally and functionally transparent to the client.
Of course the default named query itself can change what entities are returned from the data store. Thats why you wrote it. The named query may even reject the client query and throw an exception. These behaviors are up to you. The default named query applies to the cache A client query that is rooted in the default named query can be applied to the local entity cache.
If the querys QueryStrategy is Optimized or DataSourceThenCache or CacheOnly, the query will include cached entities. If the EntityManager is disconnected, the query applies to the cache. The EntityManager can also remember the query in its query cache, potentially avoiding a redundant trip to the server when the query is executed a second time. This is not true for specialized named queries described elsewhere. They are always executed as DataSourceOnly queries and are never remembered in the query cache. They always fail if executed offline. The reason for this difference is explained in the topic on specialized named queries. Only one default named query You dont have to write a default named query. If you do, there can be only one named query method per entity type. There can be only one named query method associated with an entity types EntitySet and that one method must not take parameters. Another topic covers parameterized named queries. The default named query may not have parameters.
Add specialized named query You can write a specialized named query to reflect a particular business intent such as retrieving gold customers.
A specialized named query typically supplements the default named query. It differs from the default named query in that it tends to restrict the range of queryable entities to a specific subset that represents an application domain concept. The default named query for Customer represents all customers. A specialized named query called GetGoldCustomers likely returns a highly-prized subset of those customers. You can write as many specialized named queries as you need. An example Suppose your business makes special offers for gold customers. While you could specify what makes a customer golden in the client query, for some reason you prefer to make that determination on the server. Lets write that specialized named query in a named query provider class: C# // Customers who spent more than 10,000 this year public IQueryable<Customer> GetGoldCustomers() { var thisYear = new DateTime(DateTime.Today.Year, 1, 1); return new EntityQuery<Customer>() .Where(c => 10000 < c.Orders .Where(o => o.OrderDate >= thisYear) .Sum(o => o.OrderDetails.Sum(od => od.Quantity * od.UnitPrice))); } VB ' Customers who spent more than 10,000 this year Public Function GetGoldCustomers() As IQueryable(Of Customer) Dim thisYear = New Date(Date.Today.Year, 1, 1) Return New EntityQuery(Of Customer)().Where(Function(c) 10000 < c.Orders _ .Where(Function(o) o.OrderDate >= thisYear).Sum(Function(o) o.OrderDetails _ .Sum(Function(od) od.Quantity * od.UnitPrice))) End Function All named query methods return an IQueryable or an IEnumerable of an entity type. GetGoldCustomers returns an IQueryable of Customer. You could invoke this query with the following client statements: C# query-B-GoldCustomers = new EntityQuery<Customer>("GoldCustomers") .Where(c => c.StartsWith("B"));
myEntityManager.ExecuteQueryAsync(query-B-GoldCustomers, queryCallback) The named querys EntitySet name Notice that the EntitySet name provided to the EntityQuery constructor is GoldCustomers. The Customer entity type's true EntitySet name is Customers, not GoldCustomers. "Customers" refers to all Customer entities in the data source. Only some of those customers are "gold customers." You can think of GoldCustomers as a subset of the "Customers" EntitySet. As a practical matter, DevForce uses the GoldCustomers name to find the corresponding query method on the server. The EntityServer applies the DevForce query naming conventions to locate the query method, stripping the "Get" prefix from the GetGoldCustomers method name and matching the remaining GoldCustomers text to the EntitySet name in the client EntityQuery. You can use the Query attribute to identify a query method that does not conform to the conventions as explained in the query naming conventions topic. If the client query asks for a specialized named query, the EntityServer must be able to find the corresponding query method on the server; it will throw an exception if it cant find the method. Merging the named query with the original client query DevForce merges the client query with the named query by copying the LINQ clauses of the client query LINQ to the output of the named query method. The re-composition works something like this: C# mergedQuery = GetGoldCustomers().Where(c => c.StartsWith("B")); VB mergedQuery = GetGoldCustomers().Where(Function(c) c.StartsWith("B")) The re-composed query returns B customers who spent $10,000 during the current calendar year. Create a property for the named query Notice the hard-coded the "GoldCustomers" string in the client query. That is a risky practice. It's a better idea to hide that magic string inside a dedicated, query-producing property or method. The DevForce code generator does something similar when it creates EntityQuery factory properties for the entity model's custom EntityManager. The Customers property, shown here, is typical: C# using IbEm = IdeaBlade.EntityModel; ... public partial class NorthwindManager : IbEm.EntityManager { ... public IbEm.EntityQuery<Customer> Customers { get { return new IbEm.EntityQuery<Customer>("Customers", this); } } ... } VB Imports IbEm = IdeaBlade.EntityModel ... Partial Public Class NorthwindManager ... Inherits IbEm.EntityManager Public ReadOnly Property Customers() As IbEm.EntityQuery(Of Customer) Get Return New IbEm.EntityQuery(Of Customer)("Customers", Me) End Get ... End Property End Class Notice that DevForce generated NorthwindManager as a partial class. You can extend it with EntityQuery factory properties for your specialized named queries as illustrated by this GoldCustomers property: C# public partial class NorthwindManager { ... public IbEm.EntityQuery<Customer> GoldCustomers { get { return new IbEm.EntityQuery<Customer>("GoldCustomers", this); } } ... } VB Partial Public Class NorthwindManager ... Public ReadOnly Property GoldCustomers() As IbEm.EntityQuery(Of Customer) Get Return New IbEm.EntityQuery(Of Customer)("GoldCustomers", Me) End Get End Property ... End Class
The DevForce code generator will write this property for you in a future release. Now you can write a GoldCustomers query in your application code in the same manner as you would write a regular Customer query: C# customersQuery = myEntityManager.Customers.Where(...); ... goldCustomersQuery = myEntityManager.GoldCustomers.Where(...); VB customersQuery = myEntityManager.Customers.Where(...) ... goldCustomersQuery = myEntityManager.GoldCustomers.Where(...)
Specialized named queries in OData You can use OData to invoke specialized named queries as long as you create a custom EntityManager factory property for that query as just explained. You don't have to do anything to support OData queries that depend on the default named query. DevForce generated their factory properties automatically. Specialized named queries are DataSourceOnly A query rooted in a specialized named query must be executed using the DataSourceOnly QueryStrategy. DevForce converts the default Optimized QueryStrategy to DataSourceOnly automatically. The query is never remembered in the query cache, it ignores entities in cache, and it always fails if executed offline. A query that is rooted in the default named query, on the other hand, can be remembered in the query cache, can be applied to the cache, and can execute even when the application is offline. Why the difference? The default named query is associated with the entity types EntitySet. That EntitySet represents the unrestricted view of all instances of the entity type. DevForce adopts this interpretation whether or not youve written a default named query, even if your implementation of the default named query in fact restricts query results to a handful of the possible entities.
Because DevForce assumes that the default root query embraces all entities of the type, it acts as if every entity in cache is fair game. Please keep these semantics in mind when you write a default named query. The client application should be able to assume that a query for all Customers returns every Customer that the current user would ever be allowed to see. DevForce gives the opposite interpretation to a specialized named query. Presumably you wrote a specialized named query for two reasons: (1) to limit the range of possible query results to a subset of the entity domain and (2) to hide that limiting logic from the client. If you had wanted the client to know how it worked, you would have written the query as a client query. Instead, you chose to keep the logic a secret from the client by writing a specialized named query on the server. From the client perspective, a specialized named query is a black box. DevForce cannot confidently reproduce the server-side behavior of the query on the client and does not try. DevForce concludes that the query must be executed on the server and only on the server. An attempt to force an EntityManager to evaluate the query on the client must fail with an exception. Specialized named queries can take parameters You can pass parameters to a specialized named query. This topic explains how and why. Parameterized named query You can define a specialized named query that accepts parameters to constrain the behavior of the named query.
Here is a parameterized named query that retrieves Customers whose names begin with a prefix string. C# public IQueryable<Customer> GetCustomersStartingWith(string prefix) { return new EntityQuery<Customer>().Where(c => c.Name.StartsWith(prefix)); } VB Public Function GetCustomersStartingWith(ByVal prefix As String) As IQueryable(Of Customer) Return New EntityQuery(Of Customer)().Where(Function(c) c.Name.StartsWith(prefix)) End Function The client provides the prefix by adding a parameter to the client query: C# var query = IbEm.EntityQuery<Customer>("CustomersStartingWith", myEntityManager); query.Parameters.Add(new EntityQueryParameter(prefix)); VB Dim query = IbEm.EntityQuery(Of Customer)("CustomersStartingWith", myEntityManager) query.Parameters.Add(New EntityQueryParameter(prefix)) Notice the EntitySet name, CustomersStartingWith, that corresponds to the specialized named query on the server. These two lines make sense after close study but they are not easily grasped at a glance. Its a good practice to wrap them in a method that you add to the models custom EntityManager, as illustrated here: C# public partial class NorthwindManager { ... public IbEm.EntityQuery<Customer> GetCustomersStartingWith(string prefix) { var query = IbEm.EntityQuery<Customer>("CustomersStartingWith", this); query.Parameters.Add(new EntityQueryParameter(prefix)); return query; } ... } VB Partial Public Class NorthwindManager Public Function GetCustomersStartingWith(ByVal prefix As String) _ As IbEm.EntityQuery(Of Customer) Dim query = IbEm.EntityQuery(Of Customer)("CustomersStartingWith", Me) query.Parameters.Add(New EntityQueryParameter(prefix)) Return query End Function End Class The DevForce code generator will write this method for you in a future release.
Now you can write a simple GetCustomersStartingWith query in your application code: C# query = myEntityManager.CustomersStartingWith("B").Where(...); VB query = myEntityManager.CustomersStartingWith("B").Where(...) Multiple parameters, multiple overloads A parameterized named query can have as many parameters as you require. Be sure that you add parameters to the client query that are of the same type and in the same order as the parameters of the named query method on the server. You can specify the name of the parameter in the constructor of the EntityQueryParameter. That eases analysis of the query within your custom EntityServerQueryInterceptor but it doesnt influence how DevForce matches the client query to the named query on the server. The .NET reflection API doesn't reveal method parameter names. If you write two specialized named queries with the same method name, DevForce attempts to pick the best-fit overload by matching the parameters in the client query with the parameters of the candidate query methods. When to use parameterized named queries You may not need parameterized queries. It is usually easier and clearer to write a client-side query that incorporates the would-be-parameter information in the LINQ clauses. Why not get the "B" customers with this garden variety Customer query? C# query = myEntityManager.Customers.Where(c=>c.Name.StartsWith("B") && ...); VB query = myEntityManager.Customers.Where(Function(c) c.Name.StartsWith("B") AndAlso ...) When the EntityServer receives this query, it copies the LINQ clauses from the original client query to the output of the named query method as if you had written: C# GetCustomers().Where(c => c.StartsWith("B"); VB GetCustomers().Where(Function(c) c.StartsWith("B") This is efficient and effective when the named query method (GetCustomers in this case) returns an IQueryable. However, this approach can be extremely inefficient if the named query method returns an IEnumerable instead of an IQueryable. Such a method might cause the server to fetch an enormous number of entities from the database when, in fact, the client only wants a small subset of those entities.
True, the LINQ clauses from the client query reduce the transmitted results to just the entities the client requested. But this reduction occurs in server memory after the full set of entities had been retrieved and the damage done. Its usually best to write named queries that return IQueryable when you can. Sometimes you cant. For example, you might have to implement the named query with Entity SQL (ESQL); ESQL queries return IEnumerable. Here is a parameterized named query that uses DevForce ESQL to get Customers whose names begin with a prefix string: C# public IEnumerable<Customer> GetCustomersStartingWith(string prefix) { var param = new QueryParameter("prefix", prefix); var paramEsql = new ParameterizedEsql( "SELECT VALUE c FROM Customers AS c WHERE c.NAME LIKE '@prefix%'", param); var query = new PassthruEsqlQuery(typeof(Customer), paramEsql); var results = query.Execute().Cast<Customer>(); return results; } VB Public Function GetCustomersStartingWith(ByVal prefix As String) As IEnumerable(Of Customer) Dim param = New QueryParameter("prefix", prefix) Dim paramEsql = New ParameterizedEsql( _ "SELECT VALUE c FROM Customers AS c WHERE c.NAME LIKE '@prefix%'", param) Dim query = New PassthruEsqlQuery(GetType(Customer), paramEsql) Dim results = query.Execute().Cast(Of Customer)() Return results End Function The client invokes this server-side parameterized query method using the same custom client- side myEntityManager.GetCustomersStartingWith method we showed earlier: C# query = myEntityManager.CustomersStartingWith("B").Where(...); VB query = myEntityManager.CustomersStartingWith("B").Where(...) The prefix parameter contributes to the ESQL query, reducing the number of Customer Limitations The default named query method cannot take parameters. All parameterized named queries are specialized which means they are not remembered in the query cache, are not applied to entities in cache, and will fail if executed while the application is offline. You cannot refer to a parameterized named query in an OData query.
Intercept a named query All queries processed on the EntityServer including named queries pass through the DevForce EntityServerQueryInterceptor or a custom EntityServerQueryInterceptor if you wrote one. This topic describes how you can distinguish between named and unnamed queries and intervene as you deem appropriate.
Your custom EntityServerQueryInterceptor can inspect properties of the EntityQuery to determine where it came from and how it was composed. You can alter the query or even change the query results before they are transmitted to the client. Everything your interceptor could do to an unnamed client query it can do to the re-composed query derived from a named query. Cast the query as an EntityQuery The EntityServerQueryInterceptor handles every query that implements IEntityQuery. A typical first step is to determine what kind of query you are intercepting and respond accordingly. A named query is necessarily an EntityQuery so you might start by casting to EntityQuery as in the following example: C# public class MyEntityServerQueryInterceptor : EntityServerQueryInterceptor { protected override bool FilterQuery() // called before processing the query { EvaluateEntityQuery(); return base.FilterQuery(); }
private void EvaluateEntityQuery() { var entityQuery = this.Query as EntityQuery; if (null == entityQuery) return; // not an EntityQuery; ... } } VB Public Class MyEntityServerQueryInterceptor Inherits EntityServerQueryInterceptor Protected Overrides Function FilterQuery() As Boolean ' called before processing the query EvaluateEntityQuery() Return MyBase.FilterQuery() End Function
Private Sub EvaluateEntityQuery() Dim entityQuery = TryCast(Me.Query, EntityQuery) If Nothing Is entityQuery Then ' not an EntityQuery; Return End If ... End Sub End Class Check IsNamedQuery The EntityQuery.IsNamedQuery property returns true if the intercepted query was constructed from a named query. the property is called on the server inside the EntityServerQueryInterceptor. Otherwise EntityQuery.IsNamedQuery returns false and the other named-query-related properties discussed here return null or false. The EntityServerQueryInterceptor.FilterQuery virtual method is a good place to check for named queries. If you require that all client queries be routed through named queries, you can throw a custom exception when IsNamedQuery is false. You could add the following to the EvaluateEntityQuery method from the previous example: C# if (!entityQuery.IsNamedQuery) throw new NamedQueryException("Not a named query"); VB If Not entityQuery.IsNamedQuery Then Throw New NamedQueryException("Not a named query") End If The EntityServer terminates query processing and transmits the exception to the client. A more flexible approach is to maintain a whitelist of entity types which do not require named query methods. Throw the exception if the unnamed query isn't on the list: C# Type rootQueryType = entityQuery.QueryableType; // the entity type being queried if (!(entityQuery.IsNamedQuery || UnnamedQueryWhitelist.Contains(rootQueryType)) throw new NamedQueryException("Not a named query"); } VB Dim rootQueryType As Type = entityQuery.QueryableType ' the entity type being queried If Not(entityQuery.IsNamedQuery OrElse UnnamedQueryWhitelist.Contains(rootQueryType)) Then throw New NamedQueryException("Not a named query") End If Properties of named queries The following properties of the EntityQuery class return meaningful values on the server when the intercepted query is a named query. Property Description OriginalClientQuery The original query from the client exactly as it was received by the server. NamedQuery The query output of the named query method. This is the root of the query that the interceptor is evaluating. NamedQueryMethod The reflection MethodInfo of the named query method. NamedQueryResultIsEnumerable Is true if the query method returns an IEnumerable; false if IQueryable See the topics dedicated to the EntityServerQueryInterceptor to learn about other ways to intervene in query processing. IEnumerable named query A named query method returns either an I Queryableor an I Enumerableof an entity type. This topic explains the implications of each choice.
The query that the DevForce EntityServer actually processes is the result of copying LINQ clauses from the original client query and attaching them to the output of the named query method. The output of the named query can be either an IQueryable or an IEnumerable of an entity type. The difference is important. What's the difference? An I Queryableis an expression tree. The LINQ clauses from the original client query are added to that expression tree, effectively redefining the query before it is executed An I Enumerable, on the other hand, is a delegate, an impenetrable function. The LINQ clauses added from the original client query take effect after the delegate has been executed. Those clauses filter, sort, group, and page the in-memory entities produced by the delegate. Performance implications The difference can profoundly affect performance on the server and on the database. An example may help clarify. Imagine there are 10,000 customers in the database. only 100 of them have names that begin with the letter B. the client send a query that retrieves only the B customers. youve written a default named query called GetCustomers(). The EntityServer will merge the client and named queries yielding something akin to this: C# GetCustomers().Where(c => c.StartsWith("B")) VB GetCustomers().Where(Function(c) c.StartsWith("B")) Suppose the GetCustomers named query method returns IQueryable C# public IQueryable<Customer> GetCustomers() { return new EntityQuery<Customer>(); } VB
Public Function GetCustomers() As IQueryable(Of Customer) Return New EntityQuery(Of Customer)() End Function
When the re-composed query executes, it will retrieve 100 Customer entities from the database. Suppose instead that the GetCustomers named query method returns IEnumerable as in this contrived example: C# public IEnumerable<Customer> GetCustomers() { return new EntityQuery<Customer>().ToList(); } VB Public Function GetCustomers() As IEnumerable(Of Customer) Return New EntityQuery(Of Customer)().ToList() End Function The merged query has not changed. It still has a Where clause that filters for the Bs. But this time, the server fetches all 10,000 customers from the database thanks to the ToList() method. Only after the 10,000 customers arrive on the server will the Where clause kick in and filter the results down to the 100 customers the client actually wanted. The client receives only 100 customers with either GetCustomers implementation. It neither knows nor cares how the server did its job. But with the IEnumerable version of GetCustomers, the EntityServer When to use IEnumerable Clearly IEnumerable is a poor choice for Customer queries. It might be a good choice for small, stable entity lists. Imagine that your business is very popular. Many clients are constantly online, badgering the server repeatedly with requests for your product catalog. Your catalog has only 100 highly sought after items that dont change very often perhaps a few times per day. Youd like to avoid the pressure of unnecessary trips to the database by caching the product list on the EntityServer. You can do that with a named Product query. C# public IEnumerable<Product> GetProducts() { return ThreadSafeProductCatalog.GetLatestList(); // periodically self-updates } VB Public Function GetProducts() As IEnumerable(Of Product) Return ThreadSafeProductCatalog.GetLatestList() ' periodically self-updates End Function Consider this different scenario. You have a specialized named query method that cant be implemented as an IQueryable. For some reason you had to implement it with Entity SQL (ESQL); ESQL queries return IEnumerable. Here is a contrived DevForce ESQL version of GetCustomersStartingWithB C# public IEnumerable<Customer> GetCustomersStartingWithB() { var query = new PassthruEsqlQuery( typeof(Customer), "SELECT VALUE c FROM Customers AS c WHERE c.NAME LIKE 'B%'"); var results = query.Execute().Cast<Customer>(); return results; } VB Public Function GetCustomersStartingWithB() As IEnumerable(Of Customer) Dim query = New PassthruEsqlQuery(GetType(Customer), _ "SELECT VALUE c FROM Customers AS c WHERE c.NAME LIKE 'B%'") Dim results = query.Execute().Cast(Of Customer)() Return results End Function See the parameterized named queries topic for an example of a parameterized ESQL named query. Query method naming conventions When the EntityServer receives a client query request, it looks on the server for a corresponding query method such as a named query or POCO query method. It recognizes a server-side query method by its signature and name. This topic explains the DevForce query method naming convention and how to use the QueryAttribute when you can't follow the convention.
The client query At the root of a client EntityQuery is the name of the EntitySet that identifies the domain of entities to query. Here is an example client query for Customers. C# var query = new EntityQuery<Customer>("Customers", anEntityManager).Where(...); VB Dim query = New EntityQuery(Of Customer)("Customers", anEntityManager).Where(...) "Customers" is the name of EntitySet for the Customer entity type. The more familiar anEntityManager.Customers() property returns an EntityQuery that is defined in precisely this way. If your query depends upon a specialized named query, the EntitySet name will be something else. It might be "GoldCustomers" if you've defined a named query that implements that concept. Finding the server-side query method When the EntityServer receives that client query, it looks for a method on the server that corresponds to the EntityQuery's EntitySet name. If it can't find a corresponding query method on the server there could be trouble. In most cases the EntityServer throws an exception reporting its inability to find a method to go with the requested EntitySet name. There is one exception. The EntityServer can muddle through if the query's EntitySet name matches the EntitySet name of an Entity Framework entity type. Because "Customers" is the name of the EntitySet for the Customer EF type, DevForce doesn't have to find a matching query method on the server. It will try to find a server-side method ... but it can proceed without one. That's why you do not have to write a default named query method. If you do write a default named query method, make sure DevForce can find it by following the rules described here. If you wrote a server-side query method, you must help DevForce find it, either by naming the method in a way that conforms to DevForce conventions or by marking it with the DevForce Query attribute. Query naming conventions DevForce can interpret query method names that begin with any one of the following prefixes: Prefix Method name example Matching EntitySet name Get GetCustomers Customers Query QueryCustomers Customers Fetch FetchGoldCustomers GoldCustomers Retrieve RetrieveStates States Find FindWaldo Waldo Select SelectCandidates Candidates Using the Query Attribute You don't have to follow the DevForce conventions when naming a specialized named query or POCO query. You must follow the DevForce convention when naming the default named query method. You can name the query method as you please as long as you adorn it with the Query attribute. C# [Query] public IQueryable<Customer> ReturnAllCustomers() { ... } [Query] public IQueryable<Customer> GrabMeSomeGoldCustomers() { ... } [Query] public IEnumerable<State> BringBackTheStates() { ... } VB <Query()> _ Public Function ReturnAllStates() As IQueryable(Of Customer) ... <Query()> _ Public Function GrabMeSomeGoldCustomers() As IQueryable(Of Customer) ... <Query()> _ Public Function BringBackTheStates() As IEnumerable(Of State) ... Now create the client query using the full server-side method name as the EntitySet name: C# var customersQuery = new EntityQuery<Customer>("ReturnAllStates", anEntityManager); var goldCustomersQuery = new EntityQuery<Customer>("GrabMeSomeGoldCustomers", anEntityManager); var statesQuery = new EntityQuery<State>("BringBackTheStates", anEntityManager); VB Dim customersQuery = New EntityQuery(Of Customer)("ReturnAllStates", anEntityManager) Dim goldCustomersQuery = New EntityQuery(Of Customer)("GrabMeSomeGoldCustomers", anEntityManager) Dim statesQuery = New EntityQuery(Of State)("BringBackTheStates", anEntityManager) Other discoverability requirements It isn't enough to name the method properly. You must also Ensure the query method signature is appropriate for the root of the EntityQuery. Define the method within a class marked by the EnableClientAccess attribute. Locate that class in a discovered assembly. Cache only queries There are several techniques to help query and find entities in cache.
Finding entities in cache The EntityManager provides several means of finding entities in its entity cache. In DevForce-speak, "find" means to perform a search of cache only without attempting to access the server or datastore. Finding an entity by its EntityKey If you know the EntityKey of an entity, you can use the FindEntity method to find the entity in the EntityManager cache. This is an efficient way both to locate the entity or to prove that the entity is not in cache. C# Employee emp = (Employee) Manager.FindEntity(new EntityKey(typeof (Employee), 1)); VB Dim emp As Employee = CType(Manager.FindEntity(New EntityKey(GetType(Employee), 1)), Employee)
If the requested entity is not found a null (nothing) will be returned from the FindEntity call. Note that the FindEntity call returns an object, so you must cast the returned object to the entity type expected. You can also use FindEntity to find an entity with an EntityState of Deleted. This is the only means of locating an as-yet-unsaved deleted entity. C# var key = new EntityKey(typeof (Employee), 1); Employee emp = (Employee) Manager.FindEntity(key), includeDeleted: true ); VB Dim key = New EntityKey(GetType(Employee), 1) Dim emp As Employee = CType(Manager.FindEntity(key), Employee), includeDeleted As Employee Finding entities by EntityState You can also look for entities in cache based on their EntityState. You can combine EntityStates too, for example to look for all entities which are either Added or Modified. FindEntities comes in both generic and non-generic forms, and will return either an IEnumerable or IEnumerable<T>. If using the non-generic overload you can still cast the results to an IEnumerable<T> in order to build a composable LINQ query. C# var allAdded = manager.FindEntities(EntityState.Added); VB Dim allAdded = manager.FindEntities(EntityState.Added)
C# var empList= manager.FindEntities<Employee>(EntityState.Unchanged); VB Dim empList = manager.FindEntities(Of Employee)(EntityState.Unchanged)
Using LINQ you can do more interesting things with the results: C# var addedCustomersInUK = manager.FindEntities<Customer>(EntityState.Added).Where(c => c.Country == "UK"); // .. or .. var addedCustomersInUK = manager.FindEntities(EntityState.Added).OfType<Customer>().Where(c => c.Country == "UK"); VB Dim addedCustomersInUK = manager.FindEntities(Of Customer)(EntityState.Added).Where(Function(c) c.Country = "UK") ' .. or .. Dim addedCustomersInUK = manager.FindEntities(EntityState.Added).OfType(Of Customer)().Where(Function(c) c.Country = "UK") Finding an object graph The FindEntityGraph method allows you to search the entity cache for an object graph (aka entity graph) - a list of entities that are navigable from one or more root entities according to a specified graph. Working with an entity graph is particularly useful when performing a save or import and you wish to work with only the object and its dependent entities. The FindEntityGraph method takes one or more "root" entities, effectively the starting point of the graph, and one or more EntitySpans , information about the entity relationships to be followed to build the graph. The FindEntityGraph method also allows you to specify the EntityState(s) wanted. This sounds more complicated that it really is, so here's a simple example. Here we build an entity graph of all orders and order details for a specific employee: C# var emp = Manager.FindEntity(new EntityKey(typeof(Employee), 1)); var span = new EntitySpan(typeof(Employee), EntityRelations.FK_Order_Employee, EntityRelations.FK_OrderDetail_Order); var entityGraph = Manager.FindEntityGraph(new[] { emp }, new[] { span }, EntityState.AllButDetached); VB Dim emp = Manager.FindEntity(New EntityKey(GetType(Employee), 1)) Dim span = New EntitySpan(GetType(Employee), EntityRelations.FK_Order_Employee, _ EntityRelations.FK_OrderDetail_Order) Dim entityGraph = Manager.FindEntityGraph( { emp }, { span }, EntityState.AllButDetached) Here's a more complex example. The EntityManager must be able to "walk" each EntitySpan, in the case above we navigated from Employee -> Order -> OrderDetail. If a relationship can't be walked, then you must supply another EntitySpan for it. Here we also want to retrieve all Customers for the selected Orders. To do so we need to include another EntitySpan, one which walks from Employee -> Order -> Customer. C# var emp = Manager.FindEntity(new EntityKey(typeof(Employee), 1)); var span1 = new EntitySpan(typeof(Employee), EntityRelations.FK_Order_Employee, EntityRelations.FK_OrderDetail_Order); var span2 = new EntitySpan(typeof(Employee), EntityRelations.FK_Order_Employee, EntityRelations.FK_Order_Customer); var entityGraph = Manager.FindEntityGraph(new[] { emp }, new[] { span1, span2 }, EntityState.AllButDetached); VB Dim emp = Manager.FindEntity(New EntityKey(GetType(Employee), 1)) Dim span1 = New EntitySpan(GetType(Employee), EntityRelations.FK_Order_Employee, _ EntityRelations.FK_OrderDetail_Order) Dim span2 = New EntitySpan(GetType(Employee), EntityRelations.FK_Order_Employee, _ EntityRelations.FK_Order_Customer) Dim entityGraph = Manager.FindEntityGraph( { emp }, { span1, span2 }, EntityState.AllButDetached) A few more things to note. The "roots" of the graph do not need to be of the same type. Deleted entities can be included in the graph. The "graph" is just an IList<object>, not a special type. Entities not in cache are not lazily loaded when an association is navigated. Synchronous queries in Silverlight We've already seen that any query can be given a QueryStrategy to control where it's executed. When you provide a CacheOnly query strategy, either explicitly on the query, by setting the DefaultQueryStrategy on the EntityManager, or by disconnecting, the EntityManager will run the query against the local entity cache only and not attempt to communicate with the EntityServer. Since no server communication is performed, you can execute CacheOnly queries synchronously in all environments, including Silverlight applications. For example, the following will work in any environment: C# var customers = Manager.Customers.With(QueryStrategy.CacheOnly).Execute(); VB Dim customers = Manager.Customers.With(QueryStrategy.CacheOnly).Execute()
Or alternately, C# var customers = Manager.Customers.With(QueryStrategy.CacheOnly).ToList(); VB Dim customers = Manager.Customers.With(QueryStrategy.CacheOnly).ToList() You can also issue scalar "immediate" execution queries against cache only without using AsScalarAsync . Here's a First method executed synchronously against cache: C# var aCustomer = Manager.Customers.With(QueryStrategy.CacheOnly).First(); VB Dim aCustomer = Manager.Customers.With(QueryStrategy.CacheOnly).First() In Silverlight you'll want to take care not to issue these synchronous queries without ensuring that the query will indeed be executed against cache, since an error will be thrown otherwise.
Improve query performance This page describes a number of ways to improve or optimize your query performance within a DevForce application. It has been our experience however, that much, if not most of the time, performance bottlenecks are not where a developer thinks they are. Premature optimization often results in little or no performance gains for a substantial expense in time and effort. We, therefore, strongly recommend that benchmarking be among the most important tool you have in attempting to optimize the performance of your application. In other words, benchmark before optimizing. Logging Among the most useful tools for determining if a query operation is taking too long is the DebugLog. DevForce provides the DebugFns and TraceFns classes in IdeaBlade.Core assembly that can be used to write out timings for any query. It is often important to distinguish the amount of time spent actually executing the query on the server from the amount of time spent transporting the results to the client and merging them into an EntityManager's cache. On the server, query interception and the EntityServerQueryInterceptor can be used as interception points where both a query and the amount of time it takes to execute, on the server, can be logged. On the client, the EntityManager.Querying and EntityManager.Queried (see query-client- lifecycle-events) events are good places to time the 'total cost' of a query. Look at the SQL being executed This can be accomplish either by turning on the ShouldLogSqlQueries option in the IdeaBladeConfig.LoggingElement (see App.config in detail) or using a SQL profiler. Microsoft's SQL profiler is an excellent tool for this task. Profile the Entity Framework's performance There are several tools available that profile Entity Framework performance. We recommend EFProf. Entity Framework performance tips Since most queries within DevForce get translated into calls to the Entity Framework, improving the performance of the Entity Framework query is an obvious first step. Several techniques are available: 1. Entity Framework ViewGeneration is often the most time consuming part of the query process. To determine just how much time spent here, you can use Microsoft's EDMGen tool to pre-compile a query so that the expense of ViewGeneration becomes negligible at runtime. (see http://msdn.microsoft.com/en-us/library/bb896240.aspx and http://msdn.microsoft.com/en-us/library/bb387165.aspx )
2. Add Includes -(see Using Includes). Use of the Include method can have an enormous impact on performance. In many n-tier applications the pipe between the client and the BOS is among the slowest parts of the system and multiple round trips to a server can impose a substantial performance burden. Judicious use of Includes can reduce the number of round trips substantially.
3. Remove Includes -(see Using Includes). As useful as are and despite the possibility of using them to improve performance; we have seen far too many examples of excessive use of Includes. The problem is that every Include causes a minimum of one additional query to be performed on the server. If this data is likely to be needed on the client within a relatively short period of time then it often makes sense to reduce roundtrips by 'prefetching' this data. However, if the data happens to not be needed on the server or is only needed in a small number of use cases, then allowing this data to be fetched as needed ('lazy queries') is often the much better choice.
4. Consider querying data asynchronously. Asynchronous queries operate on a separate thread and their use can often make an application appear more responsive. This is really just another form of 'eager querying', i.e. fetching the data before it is actually needed in the expectation that it will 'probably' be needed. Use of coroutines can make writing collections of asynchronous queries much easier.
5. Consider using Stored Procedure Queries or E-SQL queries instead of LINQ for queries in some situations.
6. Consider using database views and mapping some of your entities to these views.
7. After ViewGeneration, metadata lookup is the next most time consuming part of the Entity Framework. However, because metadata lookup is globally cached per Application Domain, it only occurs once when the first query is passed to a DevForce Entity Server. Obviously, this means that the cost of spinning up an EntityServer (which should occur rarely) can be expensive. This is not usually a problem with distributed n-tier (and Silverlight) applications because this effect is only felt once by the first user with his/her first query. However, when testing 2-tier, the EntityServer runs as part of the client process and is spun up each time an application restarts.
8. Consider breaking up your model into smaller submodels. Model's with more than 500 entity types can be very cumbersome and both ViewGeneration and metadata lookup can be expensive.
9. Favor TPH (Table per hierarchy) over TPT (Table per type) inheritance. (See the article, 'Performance Considerations when using TPT (Table per Type) Inheritance in the Entity Framework'.)
10. Consider using Paging when returning large result sets. In some cases the time spent moving large amounts of data over the wire can be drastically reduced by filtering the data returned with the Take and Skip operators. (See Query Paging)
Create, modify and delete Create, modify, and delete are things you do to entities while they are in memory.
Overview You saw how to retrieve entities from the database in the Query topic. Now that entities are in memory, you may want to modify them, delete them, and create new ones - the three most important activities covered in this topic. As you take these actions, your are making changes locally. These are pending changes, visible only in the current client session. Even "deleted" entities are provisional; they are merely scheduled for deletion. Your changes become permanent when you save them to the database, a subject covered separately in the Save topic. Here we concentrate exclusively on what you can do to entity instances while they are in memory. We know you want to get right down to business and start changing entities. But first we have to introduce a few fundamentals that run through the entire entity change discussion. Entity cache The entities in memory usually reside in an EntityManager's cache cache. When you query for entities from the database, they go directly into cache. That's most likely how you'll get the entities that you modify and delete. You can remove entities from the cache and manipulate them while they are detached. Note that some entity feature are available only when an entity is in cache ... which is where it should be unless you have a good reason to do otherwise. Setting properties Most of your changes involve setting data properties of the entity. It seems simple enough to set a property with a statement like: C# aCustomer.Name="Acme"; VB aCustomer.Name="Acme"
In fact, the setter of that innocent looking "Name" property is doing work under the hood that you should know about as we'll see in the Set a property topic. EntityState and EntityAspect An entity's EntityState tells you whether an entity is in cache or not. If it is in cache, it tells you if the entity has been changed or not ("Unchanged"). If it has changed, you'll want to know if it is new ("Added"), modified ("Modified"), or scheduled for deletion ("Deleted"). Many of the things you do to an entity - actions described in this section - will change its EntityState. Keep your eye on that EntityState. Every entity has an EntityState but you won't see it listed among the entity's properties. You will see an EntityAspect property that returns an EntityAspect object. The EntityAspect holds the EntityState ... and exposes a goodly number of other members that concern the intersection between the entity and the DevForce infrastructure. You'll need the EntityAspect to get to some of the features discussed in this section. Create Create and manipulate a new entity that you intend to insert into the database.
Overview Most applications can add new entities to the database. The essential steps are 1. Instantiate the entity object. 2. Set its unique EntityKey. 3. Initialize some properties as appropriate. 4. Add the new entity to an EntityManager. Steps #1 and #4 are always required. Step #1 must come first; steps #2, #3, and #4 may occur in any order. You can write the code to create an entity in many ways. Some ways are more maintainable than others. We'll recommend some practices as we describe the technical details. Create the entity Most developers create an entity by calling one of its constructors, perhaps the default constructor which every entity must have. C# cust = new Customer(); // the implicit default constructor VB cust = new Customer() ' the implicit default constructor Set the key Every entity must have a unique EntityKey before it can be added to an EntityManager and saved to the database. Key setting is a topic unto itself; you may have to generate keys with custom code. Initialize properties Before you write code to initialize properties in a custom constructor, it helps to know how DevForce initializes them first. You may not need to initialize properties in code. We're talking about DevForce entity data properties. Your custom properties and POCO entity initializations are up to you. Standard default values The data properties of a new entity return default values until you initialize them: .NET primitive properties return their default values o numeric properties return zero o string properties return the empty string o nullable struct properties (e.g., int? and Nullable(Of Integer)) return their null instances. ComplexType properties return ComplexType objects whose values are initialized as described here. Reference navigation properties return the null value until attached to an EntityManager; then they behave like active navigation properties. Collection navigation properties return an empty list until attached to an EntityManager; then they behave like active navigation properties. EDM default values The Entity Data Model (EDM) may hold default values for some of the properties, values that were derived from the database or entered manually through the EDM Designer. DevForce uses these model default values instead of the usual defaults. Custom default values We can't set a default value for some data types, such as DateTime, in the Entity Data Model, but we can still set a default at run time. Set a default value function You can modify or replace the DevForce-defined defaults by setting a DefaultValueFunction. This is set once on the EntityMetadata class, and allows you to customize the default values for data types throughout your model. This is particularly useful with DateTime and DateTimeOffset data properties, since the DevForce defaults for these assume local time. You can set only one DefaultValueFunction within your application. Here's a sample which will return DateTime.Today with DateTimeKind.Unspecified as the default value for any DataTime data property within your model, and allow DevForce defaults to be used for all other data types. C# EntityMetadata.DefaultValueFunction = (t) => { if (t == typeof(DateTime)) { return DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Unspecified); } return EntityMetadata.DevForceDefaultValueFunction(t); }; Set the DataEntityProperty.DefaultValue You can also manually set the DefaultValue to be used on specific data properties. You need to do this once only for the properties in question, and DevForce will then use these default values when new entities are created. For example, the following sets default values for several Employee data properties. C# Employee.PropertyMetadata.LastName.DefaultValue = "<Unknown>"; Employee.PropertyMetadata.BirthDate.DefaultValue = DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Unspecified); Initialization precedence In summary, the data property of a new DevForce entity is initialized as follows: 1. with the EDM default value if defined, or ... 2. with the standard or custom default value unless ... 3. the developer sets the initial value in code. DevForce decides the default value in a "lazy" manner so you can take your time initializing the property. DevForce settle upon the actual initial value when the property is first read. Note that the initialization of default values for your properties is independent of how the entity is created. You can use the CreateEntity method of the EntityManager or a simple entity constructor. Add to the EntityManager You should add the entity to an EntityManager before making it available to the caller. Navigation properties won't navigate until you do. Properties won't validate automatically. You can't save an entity until its been added to an EntityManager. Add the entity to the manager in one line: C# manager.AddEntity(cust); VB manager.AddEntity(cust) You must have have set the EntityKey - or be using an auto-generated key - before you add the entity to the manager as mentioned above. Consider writing a constructor DevForce does not generate an entity constructor. In many cases you can "new-up" the entity using its default constructor, add it to the EntityManager, as we showed above. This approach suffices in only the simplest cases. Many entities must satisfy specific invariant constraints from the moment they're born. Some child entities should only be created by their parents. Some entities have complex creation rules that you want to hide. Some entities should never be created at all. More often than not you will write a constructor. Consider writing an entity factory It may take only two lines to create an entity but it's rarely a good idea to repeat the same two lines throughout your code base. Two lines have a way of becoming three, four and more. The creation rules become fuzzy and you start to see Customer being created several different ways for no obvious reason. We urge you to wrap creation logic in factory methods - even if it is only two lines at the moment. Then decide if the factory methods should be static members of the entity class or instance methods of a separate entity factory class. Different guidance applies when creating non-root members of an Aggregate. The mechanical details of entity creation don't change but the way you arrange the code does change. Write a custom constructor You should write a custom constructor if any entity properties have required values.
Enforce invariant constraints in constructors It's a best practice to ensure (to the degree possible) that a new entity is in a valid state. You probably have business rules that stipulate which property values are always required and what values they must have when they are created. The best place to enforce those rules is in the entity constructor. You should strive to have a valid entity at all times. You should feel a bit uncomfortable when you can't reach that goal. For example, if a Customer is required always to have a real status code, we might write this constructor: C# public Customer() { Status = InitialStatus; } VB Public Sub New() Status = InitialStatus End Sub One sure invariant: every entity must have an EntityKey. You should set the EntityKey in the constructor unless it DevForce will be setting it automatically. Don't initialize every property Sometimes its better to leave properties alone. The Customer entity presumably has a Name property. If the Customer name is allowed to be empty while in memory, don't initialize it. This is a gray area. The customer name probably has a "is required" validation rule. You won't be able to save the customer without a name. You could initialize it in the constructor with a phony name such as "No Name". Technically the customer now passes the name-is- required test. This isn't substantively better than a null or empty string and you risk polluting the database with lots of "No Name" customers.
Most validation rules govern the state of the entity when it is saved. When a locally-invalid entity is harmless and there is no satisfactory way to enforce the validity check, let it go. On the other hand, when the validation rule expresses an invariant constraint - a constraint that must be true at all times - then make sure you set the property value in the constructor. When to use constructor parameters Consider writing constructors that take required parameters when essential values can't be determined within the constructor alone. Imagine this time that a valid customer name is required at all times. It may never be null. The constructor can't possibly know the actual name. Therefore, we require the name to be passed in when creating the customer. C# public Customer(string name) { EnsureValidName(Name); // throws if null, empty, or bad Name = name; } VB Public Sub New(ByVal name As String) EnsureValidName(Name) ' throws if null, empty, or bad Name = name End Sub Ehy do we need "EnsureValidName"? Won't the property setter invoke validation and catch a bad name? No it won't. Property setters validate input only when the entity is attached to an EntityManager. This entity is not yet attached to an EntityManager. The EnsureValidName method can validate the name using its own VerifierEngine if you wish.
A default parameterless constructor is required When you add a constructor with parameters, you must also add a default parameterless constructor. DevForce needs that default constructor. DevForce materializes entities from query results. To "materialize" an entity, DevForce "reconsititutes" it from server data. Sometimes DevForce can reconstitute it through a de- serialization process that by-passes all constructors. In Silverlight, DevForce reconstitutes a queried entity by calling its default, parameterless constructor. Never assume that your constructor will be called when an entity is reconstituted by query ... or by any other process. DevForce may or may not call the constructor. Don't guess. In this example, Customer has a default constructor for entity reconstitution and a parameterized constructor for entity creation: C# internal Customer() {} // required default constructor
public Customer(string name) { EnsureValidName(Name); // throws if null, empty, or bad Name = name; } VB Friend Sub New() ' required default constructor End Sub
Public Sub New(ByVal name As String) EnsureValidName(Name) ' throws if null, empty, or bad Name = name End Sub
Notice that there is no code inside the default constructor. The application will never call it. It exists only to support reconsitution of an entity during a query. Don't put anything in the default constructor unless you are sure to call it yourself. Notice also that the default constructor is no longer public. You can hide the default constructor The implicit default constructor is public so DevForce and everyone else can call it. You may not want everyone to call it. You can hide the default constructor from other assemblies but you may not hide it from DevForce. DevForce uses reflection to find the default constructor. If the entity is to be accessible in Silverlight and you don't want the constructor to be public, you can mark internal. You can't mark it private or protected because Silverlight won't let DevForce discover it. Access modifier constraints are explained elsewhere in connection with entity model definition.
Don't add unnecessary constructor parameters Minimize the number of constructor parameters and don't let them be optional. Use constructor parameters only for values that absolutely must be passed in from outside. You can set values for constrained entity properties within the constructor itself. You can set optional properties from the outside, after constructing the instance. Set the EntityKey in the constructor Set the entity's unique EntityKey in the constructor unless it is store-generated or managed by a custom key generator.
EntityKeys in brief Every entity instances has an EntityKey that uniquely identifies and distinguishes it from every other entity. The EntityKey also uniquely identifies the corresponding object in the database. You can't put an entity in an EntityManager cache until it has an EntityKey value. No two entities with the same EntityKey value can coexist in an entity cache. You can ask an entity instance for its EntityKey: C# var key = someEntity.EntityAspect.EntityKey; var id = key.values[0]; VB Dim key = someEntity.EntityAspect.EntityKey Dim id = key.values(0) An EntityKey instance holds one or more key values. Each is the value of a property of the entity. That property is called a "key property". It was defined during the mapping exercise when you built your EntityModel. An entity class can have multiple key properties (a "composite key"); most entity classes only have one key property, typically called the "ID". Whether it's an ID or a composite key, your objective as a creator of entity objects is to set the EntityKey as quickly as possible so that it can enter the cache and be displayed, modified and saved. You don't set auto-generated keys You don't have to set the key if it is "store generated". An ID property mapped to an Identity column of a table in SQL Server is a typical example of a store-generated EntityKey. DevForce will initialize such a key for you. In fact, you can't assign such a key and make it stick. DevForce will override your setting with a temporary value the moment you add the entity to an EntityManager. Similarly, if the entity requires a custom id, you'll have to wait until you add the entity to an EntityManager before you initialize the key by calling EntityManager.GenerateId(...) as described in the "Generate custom ids" topic. You definitely want to wrap this complexity in a factory method. If you don't have a store-generated key or a custom generated key, you must set the key yourself and perhaps the best place to do that is in the constructor. There are two ways to set the key: set it directly using a local key generator or set it using parameters passed into the constructor. Set a GUID key in the constructor A GUID makes a fine key in part because it is easy to generate locally. You should set the GUID key in the constructor. C# public Customer() { ID = SystemGuid.GetNewGuid(); } VB Public Sub New() ID = SystemGuid.GetNewGuid() End Sub You ask "What is SystemGuid.GetNewGuid()? There is no SystemGuid class in .NET !" Right you are. SystemGuid is a utility class that you should consider adding to your application because it helps you control what kind of Guid you create and makes it easier to write repeatable tests of your entity creation logic. Set a key with constructor parameters Sometimes the key for the new entity should be the same as the key of another entity. This typically happens when a root entity has an optional companion that extends the root with extra information. The companion is a "dependent" entity in a one-to-one relationship with its parent "principal" entity. The companion's key is always the same as its parent's key. The principal entity usually maps to one table in the database and the dependent entity to a different table. You could model them together as a single conceptual entity in Entity Framework. However, practical reasons may oblige you to model them as separate entities as we're doing in the following example. Suppose we have a Customer entity and a CustomerExtra entity. Maybe the CustomerExtra carries a potentially giant image of the Customer. The Customer entity is lean and light so you can afford to retrieve lots of Customer entities. You don't worry about the cost of the image. You only need the image when the user drills-in for more detail about the customer; that's when you'll retrieve its companion CustomerExtra. The Customer entity is the principal, CustomerExtra is the dependent, and you can navigate between them via their reciprocal reference properties. CustomerExtra's ID is always the same as its parent Customer's ID. The CustomerExtra constructor needs one parameter, the parent customerID. It might look like this: C# public CustomerExtra(int parentCustomerID) { CustomerID = parentCustomerID; } VB Public Sub New(ByVal parentCustomerID As Integer) CustomerID = parentCustomerID End Sub Set composite keys with constructor parameters A "linking" entity associates two other entities in a many-to-many relationship. The Northwind OrderDetail entity has a key with two parts, a composite key. One part is the ID of the parent Order. The other is the ID of the associated product, the product being purchased. the two parts of the key are simultaneously the foreign key ids supporting the navigation properties - OrderDetail.Order and OrderDetail.Product. The OrderDetail has to have a complete composite key - both ids - before it can be added to an EntityManager. These ids can't be calculated; they are invariants supplied at the moment of creation. The constructor could look like this. C# public OrderDetail(int parentOrderID, int productID) { CustomerID = parentCustomerID; ProductID = productID; } VB Public Sub New(ByVal parentOrderID As Integer, ByVal productID As Integer) CustomerID = parentCustomerID ProductID = productID End Sub
Consider entity parameters instead of ids In the previous example, the constructor parameters were the ids of related entities. The caller of the OrderDetail constructor would probably extract the ids from the parent Order and Product and "new" an OrderDetail like so: C# var newDetail = new OrderDetail(anOrder.ID, aProduct.ID); VB Dim newDetail = new OrderDetail(anOrder.ID, aProduct.ID)
There are times when when this kind of constructor is useful ... even necessary. More often it is better to pass the entities rather than ids. First, it's usually easier on the caller who has the Order and Product entities in hand. The caller doesn't have to bother with or even know about their idss; it just passes Order and Product. C# var newDetail = new OrderDetail(anOrder, aProduct); VB DIm newDetail = new OrderDetail(anOrder, aProduct)
And it lets the alternative constructor do the dirty work: C# public OrderDetail(Order parentOrder, Product product) { EnsureOrderAndProductAreGood(parentOrder, product); OrderID = parentOrder.ID; ProductID = product.ID; } VB Public Sub New(ByVal parentOrder As Order, ByVal product As Product) EnsureOrderAndProductAreGood(parentOrder, product) OrderID = parentOrder.ID ProductID = product.ID End Sub The more important reason to prefer passing entities is that it enables richer input validation. The "Ensure..." method can do more than check for null. It can validate the Order and the Product; make sure they're well matched; make sure that the Order is valid for this detail (and vice versa). Lesson: prefer constructors with entity parameters. Postpone adding to manager The constructor could have set the Order and Product reference navigation properties instead of setting the corresponding foreign key ids. The implementation might have been this: C# public OrderDetail(Order parentOrder, Product product) { EnsureOrderAndProductAreGood(parentOrder, product); Order = parentOrder; Product = product; } VB Public Sub New(ByVal parentOrder As Order, ByVal product As Product) EnsureOrderAndProductAreGood(parentOrder, product) Order = parentOrder Product = product End Sub But it wasn't. The author chose to set the ids instead of the navigation property because he didn't want to add a new OrderDetail to an EntityManager just yet. The author understood that if he set the navigation property - the Order property in this case - DevForce would immediately add the OrderDetail to the EntityManager of the parent Order. That's the expected behavior described in the "Add, attach, and remove" topic. He did not want to add it to a manager. Perhaps the author was hoping use this constructor in OrderDetail tests. Perhaps he planned to create design-time OrderDetail entities inside Blend. Testing and designing are easier if the new OrderDetail is attached to an EntityManager in the manner of a pre-exising entity rather than added as a new entity . He kept these options open.
Create a factory method As you start to write your entity creation code, you'll wonder "where do I put it?". Where you put it does matter. We strongly recommend that you use a factory method. Beyond that, you have choices, each with its pros and cons. We cover some of the choices and considerations in this topic.
Introduction Where do you put the code that creates an entity? It depends on the entity, the circumstances, and you're preferred style. You have to make your own choices. We'll suggest some ways to think about the choices in this topic. Whatever you decide to do, we urge you to create entities within a factory method. Use a factory method A "factory" method is a method that creates an object. We strongly discourage sprinkling bare creation logic about your code base. You don't want to find a line like this in your application. C# new Customer(); // more to follow VB New(Customer() ' more to follow This firm advice applies even if a perfectly well formed Customer is easy to create that way. Sure it is tempting. It is acceptable inside test methods and wherever you create test entities. But don't do this anywhere in the application code base. Why? Because it never stays this simple. Over time, you and other developers will find multiple, conflicting ways to create a customer that ruin your chances of a consistent, maintainable application. Just say "no". Here's a simple factory method: C# public Customer CreateCustomer() { return new Customer(); // more to follow } VB Public Function CreateCustomer() As Customer Return New Customer() ' more to follow End Function It bears repeating Do encapsulate creation logic in a factory method
"New" the entity; avoid "CreateEntity(...)" We recommend that you instantiate the entity with "new" as seen throughout this topic. DevForce offers an alternative approach using the entity factory build into the EntityManager. The previous example could have been written: C# public Customer CreateCustomer(EntityManager manager) { return manager.CreateEntity<Customer>(); // more to follow } VB Public Function CreateCustomer(ByVal manager As EntityManager) As Customer Return manager.CreateEntity(Of Customer)() ' more to follow End Function Although this approach requires the help of an EntityManager, it doesn't actually add the new Customer to the manager. It differs from "new" in these respects: 1. It uses the default constructor internally to instantiate the entity 2. It initializes the entity data properties with the default values defined in the Entity Data Model (if any) but only if the default constructor didn't change them first. 3. It hides a (replaceable) reference to the EntityManager inside the created entity. Consider a factory without "AddToManager" The following is advice that stems from a design-friendly and test-oriented mentality. Remember that adding the new entity to an EntityManager is one of the critical steps to creating an entity. You won't be able to do much with your entity until you take that last step and add the entity to an EntityManager. You won't forget that step if you include it in the factory method: C# // This is ok public Customer CreateCustomer(EntityManager manager) { if (null == manager) { /* throw exception */ } var cust = new Customer(); // other stuff manager.AddToManager(cust); return cust; } VB ' This is ok Public Function CreateCustomer(ByVal manager As EntityManager) As Customer If Nothing Is manager Then ' throw exception End If Dim cust = New Customer() ' other stuff manager.AddToManager(cust) Return cust End Function It's ok but it's not a best practice because it combines two distinct concerns: (1) making a valid Customer and (2) integrating with the DevForce infrastructure. That makes entity creation harder to test than it should be. I can't evaluate my Customer creation rules without providing an EntityManager - a requirement that has nothing to do with what I'm trying to test. It also complicates the visual design process in XAML platforms because adding an entity to an EntityManager may be blocked in Blend. Consider breaking the CreateCustomer method into two overloads: C# // Can create without adding to manager public Customer CreateCustomer() { var cust = new Customer(); // other stuff return cust; }
public Customer CreateCustomer(EntityManager manager) { if (null == manager) { { /* throw exception */ } var cust = CreateCustomer(); manager.AddToManager(cust); return cust; } VB ' Can create without adding to manager Public Function CreateCustomer() As Customer Dim cust = New Customer() ' other stuff Return cust End Function
Public Function CreateCustomer(ByVal manager As EntityManager) As Customer If Nothing Is manager Then ' throw exception Dim cust = CreateCustomer() manager.AddToManager(cust) Return cust End If Consider intention-revealing factory methods In many applications you intentionally create different versions of the same type of entity. You might create "Gold Customers" and "Silver Customers" and "Platinum Customers". You're always creating Customer entities but you configure each variation differently. Perhaps they differ only in a few property values. They could differ more substantially as when creating a "Platinum" customer triggers a sequence of ancillary activities and entity creations that are not available to "Silver" Customers. It might take more information - more and different parameters - to create a "Platinum" customer than a "Silver" customer. Why not create different factory methods for each variation? C# public Customer CreateSilverCustomer(p1, p2) {} public Customer CreateGoldCustomer{p1, p2, p3) {} public Customer CreatePlatinumCustomer{p1, p4, platinumOptions) {} VB Public Customer CreateSilverCustomer(p1, p2) End Sub Public Customer CreateGoldCustomer(p1, p2, p3) End Sub Public Customer CreatePlatinumCustomer(p1, p4, platinumOptions) End Sub The names express the difference clearly and in business terms. Each method can be tailored to its purpose, as complex or as simple as it needs to be. You can add as many Create methods as you want and add more over time without fear of breaking the core Customer class. You don't dare do this with different constructor overloads. Constructors can only have one name (the name of the class). Multiple overloads are confusing. You shouldn't change the Customer class everytime you dream up a new flavor of Customer. Keep constructors lean. They should enforce the invariant constraints of the entity and little else. Generate custom ids You have to write a custom id generator when a new entity's permanent key must be calculated with information only accessible on the server. This topic covers how to write an implementation of the DevForce IIdGenerator interface for that purpose.
Overview Every newly created entity requires a permanent unique key to be stored in the database. The key could be generated by the database or it could be assigned in creation code on the client (e.g, with a GUID). These are the simple cases covered in the entity creation topic. Sometimes you have to calculate the key on the middle tier server. The database can't generate it and you can't determine it on the client because a critical ingredient in your key definition scheme is only accessible on the server. You'll have to write a custom id generator. Your custom id generator is a class that implements the DevForce IdeaBlade.EntityModel.IIdGenerator. You deploy this class on both the server and the client and let DevForce discover it. A scenario Imagine that your application data are stored in a legacy relational database. The tables have integer key columns but key values are not store-generated ... and you are not allowed to modify the database so that they can be store-generated. Instead, the database has a special table of ids called the NextId table. It has a single row and a single column holding the next available integer id. To get a new id, you'll have to read the current "next id" value, increment the "next id" value in the table, and save. Of course you'll have to worry about concurrency; you must prevent some other process from reading the same "next id" value or overwriting your "next id" value. So you either lock the table or perform these operations within a transaction. You can't let remote clients perform this operation even if they could. The contention for that one precious "next id" value would be enormous. There are many potential clients. There is only one or maybe a few servers. We'll have to let the server(s) manage the "next id". You put the code to manage the "next id" in your custom id generator class. An example Most tables in the NorthwindIB tutorial database that ships with DevForce are defined with store-generated integer ids. The Territory table does not. If you map a Territory entity to the Territory table, it will have an integer id property ("TerritoryID") that you'll have to manage with a custom id generator. The NorthwindIB tutorial database also has a NextId table. The "Custom Id Code Generation" code sample [LINK] includes a custom id generator class that uses this table to calculate permanent ids. You would not map the NextId table to a "NextId" entity nor use Entity Framework to maintain that table. You would use raw ADO.NET commands as illustrated in the code sample. We refer to Territory and the NextId table throughout this topic. Temporary ids In principle, the client could ask your server-side id generator for the "next id" every time it created a new entity. That would be inefficient. It won't work at all if the client is disconnected from the server. DevForce is designed to work disconnected. We have to be able to create new entities without accessing the server. The solution is to use temporary ids and replace them with permanent ids before save. Accordingly, when you create a new entity, you give it a key with a temporary id. More precisely, you tell a DevForce EntityManager to give the new entity a temporary id as shown in this example of a static Create method for Territory. C# public static Territory Create( EntityManager mgr, string territoryDescription, int regionID) { Territory newTerritory = new Territory();
newTerritory.EntityAspect.AddToManager(); return newTerritory; } VB Public Shared Function Create( _ ByVal mgr As EntityManager, _ ByVal territoryDescription As String, ByVal regionID As Integer) As Territory
newTerritory.EntityAspect.AddToManager() Return newTerritory End Function The critical line is the GenerateId method of the EntityManager ("mgr") where we supply the new Territory entity ("newTerritory") and identify the property that gets the generated id. C# mgr.GenerateId(newTerritory, Territory.PropertyMetadata.TerritoryID); VB mgr.GenerateId(newTerritory, Territory.PropertyMetadata.TerritoryID) We have to tell the GenerateId method which property to generate. The key could be a composite made up of multiple properties. DevForce can't guess which of those properties requires a temporary id. If the key consisted of two id properties, both of them custom generated, we'd have to make two calls to GeneratId, each identifying the property to set. How does DevForce know what temporary id value to assign? DevForce doesn't know. Your custom id generator does. You specify how to create a temporary id in the GetNextTempId method of your generator. The DevForce GenerateId method calls your GetNextTempId and remembers that temporary id in the TempIds collection that you also implemented in your generator. NorthwindIB Territory ids are always positive integers. A negative integer is a good candidate as a temporary id. We pick an arbitrary seed (such as -100) and decrement it each time we need a new id. Let's suppose that your generator returned a temporary id of -101 and that the EntityManager's GenerateId method assigned -101 to the "newTerritory" entity. The "newTerritory" entity can carry that value around as long as it stays on the client. We'll only have to replace it with a permanent id when we save it. More on that soon. Temporary foreign key ids NorthwindIB has an EmployeeTerritory table which associates Employees and Territories in a many-to-many relationship. EmployeeTerritory has a "Territory" navigation property that returns the related Territory entity. It also has a "TerritoryID" property which holds the id of that related Territory entity. "TerritoryID" is a foreign key id ("FK Id") property. Suppose we had an instance of EmployeeTerritory called "someEmpTer" and we set its "Territory" property with with "newTerritory". C# someEmpTer.Territory = newTerritory; VB someEmpTer.Territory = newTerritory Now we have -101 in two places: once as the primary key of "newTerritory" and again in the FK id of "someEmpTer". Everything is fine until we save. Before we can save, we'll have to replace the -101 values in both places with the permanent id for "newTerritory" ... an id value we don't have yet. Id Fix-up DevForce replaces all temporary id values with permanent values when we finally ask the EntityManager to save the changes. This process is called "Id Fix-up", and occurs during the save. In the fix-up process: DevForce on the client sends the temporary ids to the EntityServer, along with all entities to be saved. The EntityServer asks your custom id generator's GetRealIdMap method to produce a dictionary of temporary/permanent id pairs. Your GetRealIdMap gets the permanent ids (perhaps from a NextId table as discussed earlier) and pairs them with the temporary ids, and returns this dictionary. The EntityServer uses this mapping of temp-to-perm ids to perform a fix-up on entities to be saved, and then performs the save. The EntityServer returns the temp-to-perm id map to the client, along with the saved entities. DevForce merges the saved entities into the client's cache, using the temp-to-perm id map for guidance. DevForce tells your client-side id generator to reset itself and clear the list of temporary ids. You don't have to wait for save to fix-up the ids. You can call the EntityManager's ForceIdFixup (or ForceIdFixupAsync) methods any time. Observations The NextId table is a familiar example that comes in many variations. In NorthwindIB there is a single "next id" value for all entities that need custom id generation. Another database might have different "next id" values for each entity type. Your application might not have a NextId table. It might have some other source of "next id" values. The commonality running through all custom id generators is that id calculation depends upon an external resource that can only be accessed on the server. An application could have a mix of id generation strategies. Most of the NorthwindIB tables have auto-increment (identity) columns that don't require the attention of your custom id generator. Territory is the exception. Perhaps you noticed that DevForce calls some id generator methods on the server and calls others on the client (and it calls a few members on both tiers). The specifics are detailed in the API documentation. Most developers will write one class and deploy it to both the client and the server. The easiest way to deploy it is simply to include it in the same project as your entity classes; you'll deploy that project to both client and server anyway. If the single-class approach is not to your liking, you can write two classes that implement the IIdGenerator interface, one for the client and one for the server. It's up to you to ensure that they are compatible. You don't have to reference the custom id generator class in any of your projects. DevForce will discover it using its MEF-based extensibility mechanism. Sample Id Generator See the code samples for the source code of an example numeric id generator class that you can either use directly or adapt for your application Block entity creation with an internal constructor You may want to prevent application developers from creating new instances of certain entity types. You will need a "defense in depth" on both client and server to block the determined developer. In this topic we suggest that you block entity construction by making all constructors internal.
Fixed-set entity types In every application there are "fixed-set" entity types. The number of states in the USA is fifty and that is not expected to change. If U.S. states are modelled as entities, you want to make it difficult to add a new state by accident. You probably want to prevent modifications and deletions too, subjects covered elsewhere [LINK]. It's a good idea to add a save interceptor rule [LINK] that rejects any client's attempt to save a change to a U.S. State or to alter the set of U.S. states. Unfortunately, a developer won't know that changes are forbidden until runtime when the application throws an exception. If you make the entity difficult to construct in the first place, the developer won't be tempted to make a mistake. Write an internal default constructor Consider writing an internal default constructor. Make all other constructors internal as well. C# internal UsaState() { } // Do not create new states VB Friend Sub New() ' Do not create new states End Sub In Silverlight application you must use internal, not private or protected. DevForce has to "materialize" UsaState objects retrieved by a query. Conceptually queries reconstitute existing entities; they don't create new entities. In practice, "materialization" may involve calling a class constructor. DevForce can call an entity's internal constructor; it can't call a private or protected constructor in Silverlight. Leave a back-door Make it difficult to create an entity but not impossible. You don't want to query for test entities and you can't query for design entities. You need a back-door to create them in memory and make it look like you queried for them - a technique covered elsewhere [LINK]. The internal constructor may be sufficient. Alternatively, you can add a static CreateForTest method to the entity class that is clearly limited in intent. Surround it in compiler directives to ensure it doesn't show up in your release builds. C# #if DEBUG ///<summary>Internal use only</summary> public static UsaState CreateForTest { return new UsaState(); } #endif VB #If DEBUG Then '''<summary>Internal use only</summary> Public Shared ReadOnly Property CreateForTest() As UsaState Return New UsaState() End Property #End If
Replace temporary foreign key ids with permanent ids This topic explains how DevForce replaces temporary foreign key id values with permanent ids during id fix-up. Foreign key id fix-up is phase withing the larger fix-up process. This topic is only relevant when ids are generated by the database or by a custom id generator.
Foreign Key Ids When two entities are related, the dependent entity (child entity) holds the key of the related principal entity (parent entity) in a foreign key property. For example, suppose an Order entity is related to a parent SalesRep entity. SaleReps have many orders. An Order has one parent SalesRep. The Order entity has a SalesRepID foreign key property that holds the ID of the parent SalesRep who took the order. Let's create a SalesRep called "newSalesRep". Assume that the key of the SalesRep entity is a store-generated integer id. When we create a new SalesRep, DevForce assigns it a temporary id automatically. That temporary id will be a negative integer. Let that temporary id be -101. Now let's create an Order called "newOrder" and set its navigation property to "newSalesRep". C# newOrder.SalesRep = newSalesRep; VB newOrder.SalesRep = newSalesRep Setting the SalesRep navigation property for "newOrder" also sets its SalesRepID property. The SalesRepID value will be -101, the same as the temporary id of "newSalesRep". Fix up The application begins to save these new entities. Because temporary ids have been generated, the fix-up process begins. The permanent id for the "newSalesRep" is determined; let's suppose the value is 300. The fix-up process knows about relationships between the SalesRep and other entities. In particular, it knows about SalesRep's relationship with Order and it knows about Order's SalesRepID. Accordingly, it finds the "newOrder" and replaces its current SalesRepID value of -101 with 300, the permanent id of "newSalesRep". "newOrder" has been fixed-up and is ready to be saved to the database. This all happens automatically. Fix up failure All works fine as long as every entity relationship is defined in the DevForce metadata. Such will be the case when the DevForce entity classes are generated from an Entity Framework Entity Data Model (EDM) which accurately reflects every pertinent foreign key relationship in the database. There is usually nothing to worry about. But it is possible to omit a relationship, especially if that relationship is not enforced by foreign key constraints in the database. The telltale sign of a problem is negative values in foreign key id columns of the database. Let's extend our previous example. Suppose that Customers are also assigned to SalesReps. The SalesRep is the parent and the Customer is the child in this relationship. Therefore, the Customer entity carries a SalesRepID that points to its parent SalesRep. Customer parallels Order in this regard ... except in one detail. DevForce doesn't know that Customer and SalesRep are related. For some reason there is no association between them. Customer has a SalesRepID property but no known relation to SalesRep and no navigation property to SalesRep either. It just has the SalesRep's id. A missing relationship is a critical omission, as we will see. This time we'll start with an existing Customer, held in a variable called "aCustomer". We're intent on reassigning this customer to the "newSalesRep" which we just created. Although there is no SalesRep navigation property, we are determined to establish the association implicitly by setting "aCustomer"'s SalesRepID directly. C# aCustomer.SalesRepID = newSalesRep.Id; VB aCustomer.SalesRepID = newSalesRep.Id The absence of the myCustomer.SalesRep navigation property should have been a warning that a critical relation might be missing. Let's see replay the fix-up scenario with this new twist: The value of newSalesRep.ID is updated to its permanent value, 300. newOrder.SalesRepID is fixed up to 300 because there is a relation between Order and SalesRep. aCustomer.SalesRepID retains the temporary id = 101 because there is no relation between Customer and SalesRep; fix-up doesn't know it should replace aCustomer's SalesRepID. Only bad things can happen as DevForce continues trying to save these entities. If the database schema actually has a foreign key constraint on the Customer's SalesRepID column, the save will fail with an exception because the database can't find a SalesRep with the negative id = -101. If there is no foreign key constraint, "aCustomer" will be written to the database with a negative SalesRepID = -101. That is a data integrity violation even if there is no code or database constraint to catch it. Important: Map all entity relations. Use CreateEntity() sparingly While we recommend using "new" to create the entity within a factory method, the CreateEntity factory is available and merits some consideration. This topic describes it, when you might want to use it, and why you probably don't.
Using "new" We generally recommend that you call a constructor within a factory method when creating a new entity ... as we typically do throughout this documentation. Here's a simple factory method that uses "new". C# public Customer CreateCustomer() { return new Customer(); // more to follow } VB Public Function CreateCustomer() As Customer Return New Customer() ' more to follow End Function Introducing CreateEntity(...) DevForce offers an alternative approach that uses the CreateEntity factory method of the EntityManager. Here's the same example - or close to it - written with CreateEntity: C# public Customer CreateCustomer(EntityManager manager) { return manager.CreateEntity<Customer>(); // more to follow } VB Public Function CreateCustomer(ByVal manager As EntityManager) As Customer Return manager.CreateEntity(Of Customer)() ' more to follow End Function Although this approach requires the help of an EntityManager, it doesn't actually add the new Customer to the manager. You have to do that in a separate step. It differs substantively from "new" in these respects: 1. It uses the default constructor internally to instantiate the entity 2. It hides a reference to the EntityManager inside the created entity. Many of us fail to see the advantage of these differences. It's often inappropriate or impossible to create a new entity with a default constructor as discussed in the topic on writing a custom constructor. The embedded EntityManager is hidden, silent, and (mostly) inaccessible until after the entity is added to cache. The hidden EntityManager The CreateEntity method embeds an EntityManager within the new entity. It's presence enables the following technique for adding the entity to the EntityManager: C# cust.EntityAspect.AddToManager();
VB cust.EntityAspect.AddToManager() You can't call AddToManager on an entity you created with "new". You have to write: C# manager.AddEntity(cust);
VB manager.AddEntity(cust) The embedded EntityManager is otherwise inaccessible as demonstrated in these tests: C# var cust == manager.CreateEntity<Customer>(); // hides an EntityManager inside the entity Assert.IsNull(cust.EntityAspect.EntityManager); // EntityManager is not visible cust.EntityAspect.AddToManager(); // adds the entity to the hidden EntityManager Assert.IsNotNull( cust.EntityAspect.EntityManager()); // now you see it. VB Dim cust = manager.CreateEntity(Of Customer)() ' hides an EntityManager inside the entity Assert.IsNull(cust.EntityAspect.EntityManager) ' EntityManager is not visible cust.EntityAspect.AddToManager() ' adds the entity to the hidden EntityManager Assert.IsNotNull(cust.EntityAspect.EntityManager()) ' now you see it. Generalizing entity creation with the non-generic overload The non-generic overload of CreateEntity has potential in a few scenarios. It could be convenient if you were writing a general utility that created entities as one of its duties. C# public object CreateUserSelectedEntity(EntityManager manager, Type entityType) { var anEntity = manager.CreateEntity(entityType); // do something with it return anEntity; } VB Public Function CreateUserSelectedEntity(ByVal manager As _ EntityManager, ByVal entityType As Type) As Object Dim anEntity = manager.CreateEntity(entityType) ' do something with it Return anEntity End Function It is modestly difficult to "new" a class when you don't know its type. The DevForce CreateEntity method does it without ceremony. Conclusion Stick with "new" unless you are writing an entity creation utility.
Modify Modify entities in the entity cache.
Overview Modifications to your entities in cache is a straightforward process. You've loaded entities into the EntityManager cache - usually by querying for them, but also by creating and saving new entities, importing entities from another EntityManager, and by loading a previously saved snapshot. However the entities got there, you can then modify them as needed. These modifications will often be through your UI via data binding to the properties of your entities, allowing user changes to be pushed into the properties. To persist your changes to the data store, you'll call one of the SaveChanges overloads on the EntityManager. The generated setter Most properties of your entities have public getters and setters. You've set the access in the EDM Designer, and the generated code has dutifully carried out your wishes: C# public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } VB Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property } When the property is data bound, both its getter and setter will be invoked to retrieve and set the value. To change a property value, you'll generally use the property setter, C# myCustomer.CompanyName = "Emeryville Eats"; VB myCustomer.CompanyName = "Emeryville Eats" . The first modification to an unchanged entity causes its EntityState to become Modified. If you're working with a newly created entity, its EntityState will remain as Added. Use the EntityAspect for the entity to see its current state. Every time a property is modified DevForce will automatically raise a PropertyChanged event for the specific property name. Data bound controls will listen for these events to refresh the display. We saw the set pipeline in the "get and set a property" topic. Along with BeforeSet and AfterSet property interception, validation may be performed, and a number of events are raised in addition to PropertyChanged. Miscellaneous topics Thread safety Like the EntityManager, an entity is not thread-safe. Unlike the EntityManager, DevForce won't try to detect that the entity is being used on multiple threads. So if you decide to have a background worker massage your entities while the application user is editing them in the UI, you're writing your own recipe for disaster. If you need to use an EntityManager and its entities across threads, the best thing to do is import the entities into another manager, so that each thread has its own EntityManager to work with. Modifying EntityKey properties Once at entity has been created and saved, you cannot modify its EntityKey. You'll receive an exception when saving the change if you try. If you do find you need to modify the key, you can clone the original entity, set the new EntityKey and add the clone to the EntityManager, and delete the original entity. All entities implement ICloneable via an explicit interface implementation, so you must first cast the entity to ICloneable, for example: C# Customer customerCopy = ((ICloneable)aCustomer).Clone() as Customer; VB
Customer customerCopy = ((ICloneable)aCustomer).Clone() as Customer Changing concurrency properties Concurrency property values are often set by the data store - for example via a database trigger or a timestamp column - or with the DevForce "auto" settings. But you can also set the new concurrency value in your application code. Just remember to set the ConcurrencyStrategy to Client, and you can then set the property value as wanted, for example: C# aCustomer.LastChangeDt = DateTime.Now.ToUniversalTime(); VB aCustomer.LastChangeDt = DateTime.Now.ToUniversalTime()
Delete Similar to modifying an entity, deleting an entity is a two step process. You first mark the entity for deletion in the EntityManager's cache, and then you commit the changes to the datasource. Mark an entity for deletion You mark an entity for deletion by calling EntityAspect.Delete. C# product.EntityAspect.Delete(); VB product.EntityAspect.Delete() After calling Delete, the EntityState will be set to Deleted. Commit changes to the datasource The entity will be deleted from the datasource when you call EntityManager.SaveChanges. After a successful SaveChanges, the EntityState will become Detached and the reference to the entity is no longer valid and it should not be used. You should remove the 'deleted' entity from any lists and UI datasources before calling SaveChanges. Otherwise, any logic that operates on the entity will likely throw an exception. Delete a new entity Calling Delete on a newly created entity (one that has not been saved) will immediately discard the entity since it does not exist in the datasource. Undo an entity marked for deletion If an entity is marked for deletion, you can call EntityAspect.RejectChanges to undo the scheduled deletion. (You can also call EntityManager.RejectChanges to revert everything in the cache.) Once SaveChanges is successfully called, you can no longer reject the changes, and another save will be required to modify the datasource. To learn more about undoing changes in the client-side cache see Undo changes. Cascaded deletes If the Entity Data Model and the database have been configured to handle cascaded deletes, other dependent entities may be deleted as well. For example, deleting an Order may also result in the deletion of all of its OrderDetails. See Handle cascaded deletes for more information. Other technical considerations If you access the properties of the deleted entity, they will be the original values as they were queried from the datasource, as this represents the entity that is being deleted. Get and set a property When you get or set an entity property, DevForce includes a pipeline of actions which you can intercept.
When you created your model you specified the names of entity properties and their attributes, such as visibility. The generated code reflects your choices. Let's examine the generated property in more depth. Data properties The DataEntityProperty represents either a "simple" property, one in which the data type is a simple primitive such as int or string, or a "complex" property, a ComplexObject containing other simple and complex properties. For example: C# public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } VB Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property The salient point is that in place of a simple backing field or automatic property we see GetValue and SetValue calls on a metadata property. We saw in the model topic how a PropertyMetadata class is generated into each entity class to hold static definitions of all properties of the entity: C# public static readonly IbEm.DataEntityProperty<Customer, string> CompanyName = new IbEm.DataEntityProperty<Customer, string>( "CompanyName", false, false, IbEm.ConcurrencyStrategy.None, false); VB Public Shared ReadOnly CompanyName As New IbEm.DataEntityProperty(Of _ Customer, String)("CompanyName", False, False, IbEm.ConcurrencyStrategy.None, False) It's the DataEntityProperty which controls how the get or set is performed. When the DataEntityProperty is constructed, it populates its GetterInterceptor and SetterInterceptor, which control the get and set pipelines respectively. You won't often work directly with these interceptors, but you can both interrogate the interceptor to see its actions, and add and remove actions too. Get pipeline If property interception is enabled for the EntityGroup, which it is by default, then all appropriate BeforeGet property interceptors are invoked. These property interceptors may cancel the get altogether. The default version of the property value is retrieved. If property interception is enabled, all AfterGet property interceptors are called. These interceptors may modify the retrieved value (without modifying the backing store), or cancel the get. You can use get property interceptors to audit access to properties, to cancel access, and to modfiy the retrieved value. Don't rely on property interceptors as a robust security mechanism though. Set pipeline If property interception is enabled for the EntityGroup, which it is by default, then all BeforeSet property interceptors are invoked. These property interceptors may modify the value, and cancel the set altogether. BeforeSet verifiers are called. If verifier options specify ShouldExitOnBeforeSetError then the set does not continue if a validation error occurred. A check is made for the equality of the old and new values. If equivalent, the property set does not take place. The EntityGroup.EntityChanging event is fired. If the change is cancelled further set processing stops. The EntityGroup.EntityPropertyChanging event is fired. If the change is cancelled further set processing stops. The new value is set in the CurrentValues store for the entity. The PropertyChanged event is fired. The EntityGroup.EntityPropertyChanged event is fired. The EntityGroup.EntityChanged event is fired. AfterSet verifiers are called. If property interception is enabled for the EntityGroup, all AfterSet property interceptors are invoked. As you can see, there are a number of ways you can fine-tune this process. You'll most commonly use property interceptors to inspect and possibly modify the value to be set, and validation to ensure the correctness of the data per your validation rules. Navigation properties The NavigationEntityProperty represents either a related entity, called a scalar reference, or a list of entities, called a list reference. By default, all navigation properties are lazily loaded. In Silverlight, they're loaded using asynchronous navigation. Your first get of a property value will cause the related entity or entities to be lazily loaded into the entity cache. You can find a full discussion of navigation properties and how they are loaded in the query topic. Here we'll briefly discuss the get and set pipelines for scalar and list navigation properties. Scalar navigation properties Here's the now familiar generated property: C# public Customer Customer { get { return PropertyMetadata.Customer.GetValue(this); } set { PropertyMetadata.Customer.SetValue(this, value); } } VB Public Property CompanyName() As String Get Return PropertyMetadata.CompanyName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property ... and the corresponding NavigationScalarEntityProperty : C# public static readonly IbEm.NavigationScalarEntityProperty<OrderSummary, Customer> Customer = new IbEm.NavigationScalarEntityProperty<OrderSummary, Customer>( "Customer", true, "FK_OrderSummary_Customer", IbEm.QueryDirection.ToRole2); VB Public Shared ReadOnly Customer As New IbEm.NavigationScalarEntityProperty(Of OrderSummary, _ Customer)("Customer", True, "FK_OrderSummary_Customer", IbEm.QueryDirection.ToRole2) Like a simple property, a NavigationScalarEntityProeprty will have both a GetterInterceptor and SetterInterceptor. You can inspect and add property interceptor actions to these interceptors. Get pipeline If property interception is enabled for the EntityGroup, which it is by default, then all appropriate BeforeGet property interceptors are invoked. These property interceptors may cancel the get altogether. Determine if a query should be issued: o If the current FetchStrategy is CacheOnly or the EntityReferenceLoadStrategy is DoNotLoad, then the current ScalarEntityReference value is returned. This might be a null entity. o If the referenced entity has already been loaded it is returned. o Otherwise either an EntityKeyQuery or EntityRelationQuery is built to retrieve the related entity. An EntityKeyQuery is used when navigating to a "principal" entity if all EntityKey values are present. o The query is executed, synchronously or asynchronously depending on the setting of UseAsyncNavigation. If synchronous, the retrieved entity is returned from the get; if asynchronous, a pending entity is returned. If property interception is enabled all appropriate AfterGet property interceptors are called.
Set pipeline If property interception is enabled for the EntityGroup, which it is by default, then all appropriate BeforeSet property interceptors are invoked. These property interceptors may cancel the set altogether. BeforeSet verifiers are called. If verifier options specify ShouldExitOnBeforeSetError then the set does not continue if a validation error occurred. A check is made for the equality of the old and new values. If equivalent, the property set does not take place. The EntityGroup.EntityChanging event is fired. If the change is cancelled further set processing stops. The EntityGroup.EntityPropertyChanging event is fired for the corresponding foreign key property. If the change is cancelled further set processing stops. The new scalar reference value is set. The PropertyChanged event is fired for the foreign key property. The EntityGroup.EntityPropertyChanged event is fired for the foreign key property. The PropertyChanged event is fired for the navigation property. The EntityGroup.EntityPropertyChanged event is fired for the navigation property. The EntityGroup.EntityChanged event is fired. AfterSet verifiers are called. If property interception is enabled all appropriate AfterSet property interceptors are called. The set pipeline for a reference property is more complex than for an ordinary data property. Because both the navigation property reference and the corresponding foreign key property are set, there are more opportunities for interception and validation, and more events are raised. There is actually quite a bit more going on under the hood, such as "fixup" of keys and attaching of entities, but we won't discuss that here. List navigation properties Our generated property: C# public IbEm.RelatedEntityList<Order> Orders { get { return PropertyMetadata.Orders.GetValue(this); } } VB Public ReadOnly Property Orders() As IbEm.RelatedEntityList(Of Order) Get Return PropertyMetadata.Orders.GetValue(Me) End Get End Property We see there's no setter with a NavigationListEntityProperty , since you retrieve the list (even if empty) and add and remove entities to the list. Here's the NavigationListEntityProperty: C# public static readonly IbEm.NavigationListEntityProperty<Customer, Order> Orders = new IbEm.NavigationListEntityProperty<Customer, Order>( "Orders", true, "FK_Order_Customer", IbEm.QueryDirection.ToRole1); VB Public Shared ReadOnly Orders As New IbEm.NavigationListEntityProperty(Of Customer, Order)("Orders", _ True, "FK_Order_Customer", IbEm.QueryDirection.ToRole1) The NavigationListEntityProperty will have only a GetterInterceptor. You can inspect and add property interceptor actions to this interceptor. Get pipeline If property interception is enabled for the EntityGroup, which it is by default, then all appropriate BeforeGet property interceptors are invoked. These property interceptors may cancel the get altogether. Determine if a query should be issued: o If the current FetchStrategy is CacheOnly or the EntityReferenceLoadStrategy is DoNotLoad, then the current ListEntityReference values are returned. This might be an empty list. o If the referenced list has already been loaded it is returned. o Otherwise an EntityRelationQuery is built to retrieve the related entities. o The query is executed, synchronously or asynchronously depending on the setting of UseAsyncNavigation. If synchronous, the retrieved list of entities is returned from the get; if asynchronous, a pending entity list is returned. If property interception is enabled all appropriate AfterGet property interceptors are called.
Add and remove If a collection is not read only you can add and remove related entities. A RelatedEntityList will be read only if created from a NavigationListEntityProperty having the IsCollectionReadOnly flag set, or if the flag has been set directly on the list. The pipeline for adds and removes is similar to that of a scalar navigation property setter, since the corresponding scalar setter is what's called under the hood. Many-to-many navigation properties are the exception, however, since adds and removes in a many-to-many relationship target relationships instead of entities. Access entity internals You access the internals of an entity through the entity's EntityAspect property. EntityAspect is your gateway to the DevForce infrastructure hidden within each entity. The EntityAspect tells you whether the entity has changed and how, and allows you to perform basic functions such as adding and removing the entity from its EntityManager. This topic covers what you can do with and learn from the EntityAspect.
Hiding entity infrastructure An entity represents a thing in the application domain. A Customer represents "customer- ness". You modeled the Customer entity to suit the needs of programmers who write code about customers. The Customer entity should exhibit "customer-ness". Here is what a Customer instance looks like in IntelliSense:
Almost all members are Customer-specific. They are data properties, navigation properties, and custom methods (if we had some). A few members concern infrastructure having nothing to do with Customer per se. These are members of the Customer's base classes such as Object.GetHashCode and Object.GetType ... and Entity.EntityAspect. They are noise when our attention is on "customer-ness". They can distract us from our primary purpose - to do business with a customer. In an ideal world they would be invisible. In practice, we need them. At various times we have to set aside our "Customer" perspective and see the Customer entity as a .NET object. Sometimes we need to work with it as Object. Sometimes we need to work with it as an Entity. When we need to shift our perspective, we need to shift quickly. The architect's challenge is to keep "customer-ness" on center stage and have "object-ness" and "entity-ness" quietly ready in the wings. You have to access the entity internals of Customer from time to time. Rather than clutter Customer with lots of "entity-ness" members, DevForce wraps them in a single helper object, the EntityAspect. The EntityAspect property is the place to go for "entity-ness" - the place to go for access to the inner entity nature of a Customer. What's in EntityAspect You'll most often use EntityAspect to determine the state of the entity: is it Added, has it been changed, is it null or pending? You'll also use EntityAspect to work directly with validation errors or to access entity metadata. In general, though, you don't often need to work with the EntityAspect, as DevForce will handle the entity for you as you query, modify and save your entities. Learn the state of the entity Member Purpose EntityKey The value that uniquely identifies this entity. EntityState The state of the entity - whether unchanged, added, modified, deleted or detached. HasChanges If changes are pending for this entity. IsChanged If changes are pending for this entity, i.e., EntityState is added, modified, or deleted. IsNullEntity If this entity is the special "null entity" ("nullo"). A reference navigation property returns such a null entity when it can't return the related entity (rather than return null and risk an exception). IsPendingEntity If this entity instance is a placeholder for a real entity of this type. An entity will be pending when using asynchronous navigation and the retrieval from the database has not completed. PendingEntityResolved Raised when a pending entity has been retrieved from the database. IsNullOrPendingEntity If this entity is either the null entity or a pending entity. A quick way to tell if the entity is real and complete. Change the state of the entity Other than calling Delete, in most circumstances you won't need to use the methods described below, as DevForce will automatically handle the state of the entity as it's queried, modified, and saved. Member Purpose Delete Mark this entity for deletion; the entity will be deleted from the database when saved. RejectChanges Undo pending changes. AcceptChanges Commit pending changes locally as if the entity were saved and now unchanged. Beware! Does not update the database. You do not need to call AcceptChanges during normal save processing, as DevForce will do it for you when a save completes successfully. SetAdded Convert the state of the entity such that it appears to have been newly created. If saved, DevForce will try to insert it into the database. When you create a new entity and add it to an EntityManager, it will automatically be marked as Added and you do not need to call this method. SetModified Convert the state of the entity such that it appears to be a modified entity that came from the database. If saved, DevForce will try to find and update the corresponding database object. When you modify an entity in cache it will automatically be marked as Modified by DevForce and you do not need to call this method. GetValue Get the value of the named property. Could get the current value of the property or one of the other versions. SetValue Set the current value of the named property. ForcePropertyChanged Raise the entity's PropertyChanged event on demand. Learn about the entity's validity Member Purpose ValidationErrors Get a modifiable collection of the currently detected validation errors for this entity. VerifierEngine Get the VerifierEngine used by this entity's auto-validating property setters. The engine always comes from the entity's EntityManager. It is null if the entity is detached; for this reason property setters don't validate input automatically when the entity is detached. Work with the entity's EntityManager Member Purpose EntityManager Get the EntityManager to which the entity is attached. Null if detached. AddToManager Add the entity to its associated EntityManager as a new entity. An EntityManager is associated with an entity when the entity is created with the CreateEntity method. RemoveFromManager Detach the entity from its EntityManager. Removing an entity from its EntityManager is not the same thing as deleting an entity. The entity will not be deleted from the back end data source with RemoveFromManager. FindRelatedEntities Find cached entities related to this entity by a specific link. GetRelatedEntities Get entities related to this entity by a specific link; depending on circumstances and parameters DevForce could retrieve them from the database (asynchronously in Silverlight). EntityGroup Gets the collection of entities to which this entity belongs. When this entity is attached to an EntityManager, the EntityGroup contains this entity and every other entity of its type in the cache. EntityGroup events reveal when entities in the group are added, removed, or changed. Get Metadata about the entity Member Purpose EntityMetadata Get model metadata about this entity's type. GetProperty Get an EntityProperty metadata object by name with which you can inspect or modify property values generically. GetDataProperty Get a data meta-property by name which you can inspect or use to get and set values generically. GetNavigationProperty Get navigation meta-property by name which you can inspect or use to add and remove related entities generically. EntityAspect's own stuff Member Purpose Entity A back-reference to get the entity for which this is the EntityAspect. Wrapper A back-reference to the EntityWrapper for which this is the EntityAspect. For a DevForce generated entity the Entity and Wrapper properties return the same entity object because the entity inherits from both Entity and EntityWrapper. The distinction between Entity and EntityWrapper is relevant only for POCOs. PropertyChanged Raised by EntityAspect when certain of its own properties change such as EntityState, EntityKey, and HasChanges. Not to be confused with PropertyChanged event for the entity. Check for changes An entity's EntityState tells you if it is attached to an EntityManager, if it has pending (unsaved) changes, whether it was newly created, previously existing, or scheduled for deletion. This topic covers what these states mean, how they change, and some ways to program with EntityState.
Has it changed? No direct property of Customer can tell you if a particular Customer instance has been changed. That's not a question about "Customer-ness"; it's an entity infrastructure question. You access a Customer object's entity internals by means of its EntityAspect property. EntityAspect opens the door to a wealth of information about your entity. Three EntityAspect members can tell you if the entity has changed: HasChanges IsChanged EntityState HasChanges() and IsChanged are synonyms. One is a method; the other is a property to which you can data bind. IsChanged delegates to HasChanges(). Use either like this C# if (aCustomer.EntityAspect.IsChanged) // do something VB If (aCustomer.EntityAspect.IsChanged) Then ' do something HasChanges() is defined in terms of the EntityState. It reports that the entity has changes if the entity's EntityState is any value other than Unchanged.
A thought for later: IsChanged is always true for a Detached entity whether or not it actually had changes before it was detached. In other words, don't use HasChanges() and IsChanged with detached entities. HasChanges() and IsChanged are syntactic sugar wrapped around the property that really matter, the EntityState Introduction to EntityState EntityState addresses these three questions: Is the entity attached to an EntityManager or is it detached? Is the entity changed or not If changed, is it an added entity, a modified entity, or an entity marked for deletion? The answer is one of the IdeaBlade.EntityModel.EntityState enums returned by the EntityAspect.EntityState property. C# currentState = aCustomer.EntityAspect.EntityState; // an EntityState enum VB currentState = aCustomer.EntityAspect.EntityState ' an EntityState enum The currentState will be one of these EntityState enums : EntityState Summary Detached The entity is not attached to an EntityManager. It is in this state upon creation or after being removed from an EntityManager. Unchanged The entity is attached to the EntityManager, presumed to exist in the database, and has not changed since last queried or saved. Added The entity has been added to the EntityManager and is presumed to be new (not in the database). Modified The entity is attached to the EntityManager, presumed to exist in the database, and has been modified since last queried or saved. Deleted The entity was deleted using the Entity.Delete() method. In this topic we discuss what the states mean and how to use them. A separate topic covers the operations that changea an entity's EntityState. The Detached EntityState You usually work with an entity that is attached to an EntityManager. An attached entity is an entity that is in an EntityManager's entity cache. You can ask it for its EntityManager: C# manager = aCustomer.EntityAspect.EntityManager; VB manager = aCustomer.EntityAspect.EntityManager The variable manager has a value if the entity is attached; is is null if the entity is not attached. It's easier to check the EntityState than to test for a null EntityManager.
An entity is attached if its EntityState is not Detached. C# if (aCustomer.EntityAspect.EntityState != EntityState.Detached) // it's attached VB If (aCustomer.EntityAspect.EntityState <> EntityState.Detached) Then ' it's attached There are reasons to work with a detached entity and certain operations automatically detach an entity. Another topic covers attaching and detaching entities in more detail. Here we discuss some of the implications of being attached or detached. Certain infrastructural features only work when the entity is attached. For example, you can only get to the database via a navigation property when the entity is attached. Consider the following statement: C# aCustomer = anOrder.Customer; // get the Order's parent Customer VB aCustomer = anOrder.Customer ' get the Order's parent Customer The property behaves differently for attached and detached entities. If anOrder is attached, its aCustomer is either a real Customer entity or the special form of a Customer entity called "the null entity" (aka "the nullo") depending upon whether anOrder has a customer or not. If DevForce isn't sure, it could query the database for the parent Customer. You can check if the Order's Customer is real or the nullo. C# if (aCustomer.EntityAspect.IsNullEntity) // ... VB If (aCustomer.EntityAspect.IsNullEntity) Then ' ... If anOrder is detached, aCustomer is probably null. The anOrder object doesn't have an EntityManager so it can't find its parent Customer in cache and it has no way query the database. It might have a Customer, left over from its former life as an attached entity. But it probably doesn't. That means that the example above would throw a NullReferenceException. Don't expect automatic validation when you set a property of a Detached entity. Automatic property validation depends upon access to the EntityAspect.VerifierEngine. The VerifierEngine is null for a Detached so the data property skips the validation step. Make sure you know when you're working with a Detached entity. Use combo-EntityStates to find entities in cache EntityState is defined as a flag enum which means individual enum values can be OR'd together to represent a combination of states. C#
addedOrModified = EntityState.Added | EntityState.Modified; VB addedOrModified = EntityState.Added Or EntityState.Modified The EntityState enum includes two particularly useful combinations: EntityState Summary AnyAddedModifiedOrDeleted Added or Modified or Deleted. AllButDetached All states except detached. You can use these combo enums to select entities from the cache. C# // All entities in cache with pending changes changedEntities = manager.FindEntities(EntityState.AnyAddedModifiedOrDeleted);
// All Customer entities in cache custsInCache = manager.FindEntities<Customer>(AllButDetached); VB ' All entities in cache with pending changes changedEntities = manager.FindEntities(EntityState.AnyAddedModifiedOrDeleted)
' All Customer entities in cache custsInCache = manager.FindEntities(Of Customer)(AllButDetached) Simplify EntityState checking with extension methods You frequently ask if an entity is in a particular state or one of a common set of states. The code to get the answer is not difficult but it is tedious. C# currentState = aCustomer.EntityAspect.EntityState;
if (EntityState.Deleted == currentState) // ... if ((currentState & (EntityState.Added | EntityState.Modified)) > 0) // ... VB currentState = aCustomer.EntityAspect.EntityState
If (EntityState.Deleted = currentState) Then ' ... If ((currentState And (EntityState.Added Or EntityState.Modified)) > 0) Then ' ... These boolean EntityState extension methods make it easier to check for specific states and combination. IsDetached() IsModified() IsDeleted() IsUnchanged() IsAddedOrModified() IsAddedOrModifiedOrDeleted() IsDeletedOrDetached() IsDeletedOrModified() Here are the same state-checking statements as above, rewritten with extension methods. C# currentState = aCustomer.EntityAspect.EntityState;
if (currentState.IsDeleted()) // ... if ((currentState.IsAddedOrModified()) // ... VB currentState = aCustomer.EntityAspect.EntityState
If currentState.IsDeleted() Then '... If (currentState.IsAddedOrModified() Then '...
Change the EntityState is topic identifies many of the things you can do to an entity that change its EntityState.
Operations that change the EntityState Here's a brief summary of the EntityState-changers you'll run into most frequently: Operation Summary Query A successful query returns entities that are attached to the EntityManager. A newly-queried entity is Unchanged unless it happens to already be in cache. If the entity is already in cache, it retains its current EntityState by default. See the MergeStrategy topic for details. AddEntity Attaches the entity to the EntityManager as an Added entity. The entity is presumed to be new and unknown to the database. It would be inserted if saved. AddToManager() Attaches the entity to an inner, hidden EntityManager as a new entity. Call for an entity you created with EntityManager.CreateEntity(). See the special topic on this method. AttachEntity Attaches the entity to the EntityManager as an Unchanged entity. The entity is presumed to be in the database already. RemoveFromManager Detaches the entity from its current EntityManager. The new state is Detached. Set a property An Unchanged entity becomes Modified. Entities in other states retain their EntityState values. Save If the save succeeds, Added and Modified entities become Unchanged. Deleted entities are removed from cache and become Detached RejectChanges Modified and Deleted entities are rolled back to their pre-change values; their new EntityState is Unchanged. Added entities are removed from the EntityCache and become Detached. AcceptChanges As with save, Added and Modified entities become Unchanged. Deleted entities are removed from cache and become Detached. Save actually calls AcceptChanges. Beware: calling AcceptChanges does not save them. Call it with a good reason, e.g., building test and design-time entities. SetAdded Change the entity from its current, non-Detached, state to the Added state. A feature most useful when building test and design-time entities. SetModified Change the entity from its current, non-Detached, state to the Modified state. A feature most useful when building test and design-time entities. Listen for changes Listen for changes to specific entities or for changes to groups of entities in cache by attaching handlers to Entity and EntityManager events.
EntityManager events
The EntityManager has a number of events that provide access to a variety of information regarding changes to both the Entities within an EntityManager as well as the Instance events: Event Description Cleared Fired whenever the EntityManager.Clear method is called. EntityChanging Fired just before any entity within the EntityManager is about to be changed. The change can be cancelled from within the event handler. Information about the change is available via an EntityChangingEventArgs parameter passed to the event handler. EntityChanged Fired just after any entity within the EntityManager is changed. Information about the change is available via an EntityChangedEventArgs parameter passed to the event handler. EntityServerError Fired when an error occurs during an operation that passed data to the EntityServer. The IdeaBlade.EntityModel allows the exception to be examined and optionally "handled" so that the exception does not propogate further. This can be used as a "last resort" error handler for any EntityServer errors. Querying Discussed here Fetching Discussed here Queried Discussed here Saving Discussed here Saved Discussed here Both the EntityChanging and EntityChanged event handlers are passed an event args variable that in turn contains an instance of the EntityAction enumeration. This action may be used to determine the kind of change that is either about to occur or has just occured. The descriptions below are phrased in the past tense but should be translated to the future tense in the case of the EntityChanging event. EntityAction is a flag enumeration meaning that more than one action may set at the same time. Where this occurs, it is called out. EntityAction Description Remove The entity has been removed. Either via a call to RemoveEntity or after the save of a Deleted entity. Change The entity has changed as a result of a change to some property on the entity. Rollback The most recent change to the entity has been rolled back. This occurs as a result of a RejectChanges call. Commit The changes to the entity have been committed. This occurs as a result of a AcceptChanges call. Add The entity has just been attached to the EntityManager. ChangeOriginal The original version of the entity has been changed. This occurs as a result of a Merge operation (via Query or ImportEntities) with PreserveChangesUpdateOriginal strategy. ChangeCurrentAndOriginal The original and the current versions of the entity have been changed. This occurs on any Merge operation other than the one above. AddOnQuery The entity has been attached as a result of an query operation. (This is also an Add EntityAction) AddOnImport The entity has been attached as a result of an import operation. (This is also an Add EntityAction) AddOnAttach The entity has been attached to as a result of an AttachEntity call but with an EntityState other than Added. (This is also an Add EntityAction) Delete The entity was marked for deletion Static ( class level) events: Event Description EntityManagerCreated A class level event that is fired anytime a new EntityManager is created EntityGroup events In DevForce, every EntityManager contains a collection of EntityGroups. Each EntityGroup in turn contains all of the entities of a single entity type that are part of this EntityManager. An EntityGroup can be retrieved via either of the following methods on an EntityManager. C# public EntityGroup GetEntityGroup(Type entityType); public EntityGroup<T> GetEntityGroup<T>(); VB Public Overloads Function GetEntityGroup( _ ByVal entityType As Type _ ) As EntityGroup Public Overloads Function GetEntityGroup(Of T)() As EntityGroup(Of T) Each EntityGroup has a number of events that may be handled: Event Description EntityPropertyChanging Fired just before a property of an entity changes. The associated EntityPropertyChangingEventArgs contains information regarding the entity being changed, the property being changed and the "proposed value" for the change. The change may be cancelled, in which case the property set operation will not occur. EntityPropertyChanged Fired just after a property of an entity changes. The associated EntityPropertyChangedEventArgs contains information regarding the entity that was changed, the property that was changed and the "value" for the change. EntityChanging This is basically the same as the EntityManager.EntityChanging event simply fired at a different level. EntityChanged This is basically the same as the EntityManager.EntityChanged event simply fired at a different level. There may be cases where a developer wants to suppress all "change notification" within an EntityGroup for a period of time. This can be accomplished by setting the ChangeNotificationEnabled property to 'false'. Note that this will effect change notification at the EntityManager level (EntityChanging, EntityChanged) as well as at the Entity level (INotifyPropertyChanged). Entity events Event Description PropertyChanged Implementation of INotifyPropertyChanged ErrorsChanged Explicit implementation of INotifyDataErrorInfo EntityAspect events Most changes that relate to an Entity are handled by the events listed previously on this page. However, there are a small number of properties that are only available thru the EntityAspect of an entity. These are properties like EntityState and EntityKey and there is a seperate INotifyPropertyChanged implementation on the EntityAspect to handle notification of changes to these properties. Event Description PropertyChanged Part of INotifyPropertyChanged implementation. Properties on the EntityAspect that are subject to changed and therefore available via the PropertyChanged notification are EntityState, EntityKey, IsChanged, and HasErrors. Undo changes Undo pending (unsaved) changes to a single entities, selected entities, or all entities in cache by calling one of the RejectChanges methods. In addition, DevForce entities also support the IEditableObject interface which allows for the undo of changes within an "editing" context"
The RejectChanges method An application can undo changes made to individual entity via the EntityAspect.RejectChanges method. The same operation can be performed against all of the relevent entities within an EntityManager ( all modified, deleted, or added entities) via the EntityManager.RejectChanges method. This is a single level undo. Undoing a pre-existing object, whether added, modified or marked for deletion, restores it to its state when last retrieved from the data source; The following table describes what happens to an entity in any given state when a RejectChanges call is made on it. Original EntityState EntityState After Other side effects RejectChanges Modified Unchanged Original values become current values Added Detached Entity gets removed from the cache ( as if it had never been Added). Deleted Unchanged Entity gets "undeleted" Unchanged Unchanged Nothing actually happens here. Detached Detached Nothing actually happens here. There is no undo of an undo. DevForce Entities also implement the .NET I EditableObject interface. This interface provides for a second form of undo that is commonly utilized by UI controls when binding directly to an entity. By convention this interface is implemented explicitly and therefore requires casting. Access to "Undo" information DevForce effectively maintains a copy of each entities "original" version. When you access the properties of an entity, either via a get or set operation, you are accessing the "default" version of the entity. The property values of the "original", as well as a "proposed" version when inside of an IEditableObject edit session, however, are available as well. The values for any data property of an object are available via either of the following two overloads of the EntityAspect.GetValuemethod. This is discussed in more detail here. C# public Object GetValue(String propertyName, EntityVersion version); public Object GetValue(DataEntityProperty dataEntityProperty, EntityVersion version); VB Public Function GetValue(ByVal propertyName As String, ByVal version As EntityVersion) As Object End Function Public Function GetValue(ByVal dataEntityProperty As DataEntityProperty, ByVal version As EntityVersion) As Object End Function This value returned from the original version is usually exactly what was queried from the database, although it is possible to update the "original" to match the "current" version via either the EntityAspect.AcceptChanges or EntityManager.AcceptChanges method calls. EntityVersion is an enumeration that with the following values: Value Description Default The default version for the state of the entity. This is a version that is used to actually reference another version based on the state of the entity. A version of Default will almost always mean Current with the two exceptions being when an object is within an IEditableObject session, the default version is Proposed. when an object has an EntityState of Deleted, its default version is Original. Original The original version of the entity from when it was last queried, saved or had AcceptChanges called on it. Current The most recent version of the entity, outside of an IEditableObject session. Proposed The value of the entity when within an IEditableObject session. Undo of a change to a navigation property that returns a list
The RejectChanges method basically returns an entity to the state it was in when it was last queried or saved. If other related objects are associated with this entity by being added to or removed from a "list" navigation property then the RejectChanges call will not have any effect on the collection returned by the property. This is because information about what entities are associated with another entity is not considered part of the persistent state of the entity except in the case where a foreign key reference id exists ( or in case of a many-many relationship). Since most relationships do have a foreign key defined on one side or the other of the relationship it is often sufficient to call RejectChanges on the entities on both sides of a relationship in order to restore the "graph" to an earlier state. Another approach is to use the rollback mechanism described under the "Snapshot changes" topic. Add, attach, and remove This topic covers how to add new entities to an EntityManager's cache, how to remove entities from the cache, and how to attach other entities to the cache such that they appear to have been queried rather than created.
When do entities get attached? In order for entities to be managed by DevForce they must first be associated with an entitymanager. Entities will be associated with an EntityManager as a result of any of the following operations: A query. A navigation from one entity to another that in turn triggers a query. The creation of a new entity and its addition to an EntityManager. An entity can only be associated with a single EntityManager at one time. Every entity has an associated EntityState that indicates whether a particular entity is new, modified, marked for deletion, or unmodified. The EntityState for an entity is automatically set and updated by the DevForce infrastructure. Attaching entities to the EntityManager Once an entity has been created, it must be attached to an entitymanager before it can be saved, validated, or participate in a number of other services that the EntityManager class offers. Entities may be added to the EntityManager in two ways; either via the EntityManager EntityManager.AddEntity method or via the EntityManager EntityManager.AttachEntity method. The AttachEntity method is actually the primary workhorse here and the AddEntity method actually delegates to it. The difference between these two methods is that the AddEntity method adds the entity to an EntityManager in an "Added" state, whereas AttachEntity can add an entity to an EntityManager in any of the following EntityStates, "Unchanged", "Added" or "Modified". The AttachEntity method, by default, attaches entities in an "Unchanged" state, but the method offers an optional parameter that allows you to specify the desired resulting EntityState. The AddEntities and AttachEntities methods perform the same operations as described above but support the ability to attach collections of entities in a single call. So why would we want to attach entities in different states? EntityState Use case
Added Probably the most common case. Usually used after creating a new entity that we want to have inserted into the database on the next save. This is such a common case that the AddEntity method was created just to cover this case. Any time an entity is attached in this state DevForce also performs the task of generating and updating the entity with a 'temporary' primary key if the entity has been declared as participating in automatic id generation.
Unchanged Less common case. Usually used when reattaching a previously detached entity and we don't want any persistence operation to occur on the entity.
Modified Less comon case. Usually used when reattaching a previously detached entity and we want to be able to update the backend database with the values of this entity. Note that an entity that is attached in a modified state will preserve any of its "original values" for the purposes of determining which properties actually require modification on the database. These original values will not exist unless the entity was previously queried or saved and then detached.
In addition to the EntityManager.AddEntity method mentioned above, the EntityAspect.AddToManager method does exactly the same thing. The AddToManager method is provided because it is sometimes clearer, within an entity constructor to write code where the entity adds itself to a manager, instead of the reverse. Attaching entity graphs An entity graph is group of entities that are associated with one another via navigation properties. It is possible to 'wire up' a graph of entities before any of them have been attached to an EntityManager. When any one of these entities is then attached, all of the entities in the group will be attached as well, in whatever state was specified for the initial entity. In addition, if we ever use a navigation property on an already attached entity to reference a detached entity, the detached entity will automatically get attache, along with any of its detached relations. In this case the detached entities will all be attached in an added state. Detaching entities Entities can be removed from an EntityManager in either of the following two ways: EntityManager.RemoveEntity or EntityManager.RemoveEntities* EntityAspect.RemoveFromManager Note that 'removing' or 'detaching' an entity from the EntityManager is NOT the same as deleting it. Removing an entity from an EntityManager marks the entity as 'detached'. Detaching is basically telling the EntityManager to 'forget' the entity. Note that any dependent children of this entity will NOT be removed, they will be "orphaned", meaning that they will no longer have a parent but will still exist in the EntityManager. Deleting, a completely different operation, marks the entity as 'deleted' and schedules it for deletion during the next save operation at which point it will be removed from both the database and the EntityManager. Detached entities can be reassociated with an EntityManager via either the AddEntity or AttachEntity methods described earlier. There is one other issue related to removing or detaching entities from an EntityManager. It has to do with the state of the EntityManager's query cache after the removal of an entity. Because DevForce cannot associate a removal of an entity with the query or queries that produced it, by default, DevForce is forced to clear its query cache ( not the entity cache) when a removal occurs. This may seem drastic, but basically the query cache is there to improve performance when DevForce knows that a query has already been processed and that it can return the same results running from cache as it would when querying from a database. When any entity is removed from the cache, DevForce loses the ability to make this guarantee. This is the default behavior, but you can tell DevForce not to clear the query cache by making use of the optional second parameter of the RemoveEntity/RemoveEntities/RemoveFromManager methods. Note that the removall of Added entities will not trigger the default clearing of the query cache because these entities could never have been returned from a query. Using the AttachEntity method for testing Those of you who write tests and don't want those tests to touch the database will appreciate the ability of the AttachEntity method to be used in testing. As you know, you sometimes need to write tests which rely upon interaction with the EntityManager. You want to populate a disconnected EntityManager with a small collection of hand-rolled stub entities. While such tests are integration tests because they rely on a dependency, we still want to make them easy to write and we want them to be fast. That means we don't want a trip to a database when we run them; we shouldn't need to have a database to run them. I usually start by creating a test-oriented, disconnected EntityManager ... which can be as simple as the following: C# var testManager = new EntityManager(false /* disconnected */ ); VB Dim testManager = New EntityManager(False) ' disconnected The easiest way to get a stub entity is to "new" it up, set some of its properties, give it an EntityKey, and dump it in our testManager. When we're done it should appear there as an unchanged entity ... as if you had read it from the datastore. The catch lies in the answer to this question: "How do I add the entity to the manager?" In the absence of AttachEntity() method, you would have to use EntityManager.AddEntity(). But after AddEntity, the EntityState of the entity is always "Added". You want a state of "Unchanged" so you have to remember to call AcceptChanges (which changes the state to "Unchanged"). That's not too hard. Unfortunately, it gets messy if the key of the entity is auto-generated (e.g., mapped to a table whose id field is auto-increment) because DevForce automatically replaces your key with a temporary one as part of its auto-id-generation behavior. We could explain how to work around this, but the AttachEntity() method already does what we need. Let us elaborate here and compare it to some similar methods by calling out some facts about the following code fragment: C# theEntityManager.AttachEntity(theEntity); VB theEntityManager.AttachEntity(theEntity) theEntitys EntityKey (the key) must be preset prior to the attach operation (which will not touch the key). An exception is thrown if an entity with that key is already in the cache. After attach, theEntity is in an Unchanged EntityState (the state). theEntity is presumed to exist in the persistent store; a subsequent change and save will translate to an update statement. After a successful attach, a reference to theEntity is a reference to the entity with that key in the managers 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 theEntityManagers QueryCache. AddEntity behaves the same way as AttachEntity except as follows: After add, theEntity is in an Added state theEntity is presumed to be new and to be absent from in the persistent store; a save will translate to an insert statement. If the key for this type is auto-generated (e.g., backed by an auto-increment field in the database), the existing key will be set to a generated temporary key, replacing the prior key value. The following is true regarding detaching anEntity: After detach, anEntity enters the Detached state no matter what its prior state. Detaching an Order does not detach its child OrderDetails - they remain orphaned in the cache. The sequence of detachments is not important; an Order may be detached prior to detaching its child OrderDetails. Detach has no effect on theEntityManagers QueryCache. Difference between AttachEntities and ImportEntities EntityManager.ImportEntities 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.ImportEntities(new [] {theEntity}); VB theEntityManager.ImportEntities( {theEntity}) ImportEntities differs from AttachEntity in that: It requires a MergeStrategy to tell it what to do if an entity with the same key as "theEntity" already exists in the cache. It merges "theEntity" into the cache based on the MergeStrategy It makes a clone of "theEntity" and adds that clone to the EntityCache ... unless "theEntity" happens to already be in the cache in which case it is ignored ... which means that Using our example and assuming that "theEntity" was not already in the manager, the entity instance in the cache is not the same as the entity instance you imported, although their keys are equal; the following is true:
theEntity |= theManager.FindEntity(theEntity.EntityAspect.EntityKey) A "clone" is a copy of an entity, equivalent to calling the following: C# ((ICloneable)theEntity).Clone(); VB CType(theEntity, ICloneable).Clone() This is a copy of the entity, not of its related entities. Creating a "live" filtered list of entities The IdeaBlade.EntityModel.EntityListManager<T> class is intended to provide 'live-list" management capabilities to any list containing DevForce entities. The idea of a "live" or "managed" list is that the membership to the list is kept continuously updated based on changes to entities in an EntityManager's entity cache. Filter expressions are used to determine the rules by which an entity is either included in or excluded from a specific list.
Creating an EntityListManager
Consider the following code: C# Predicate<Employee> filter = (emp) => emp.City == "London"; _employeeEntityListManager = new EntityListManager<Employee>(_em1, filter, null); bool refreshListWhenPlacedUnderManagement = true; _employeeEntityListManager.ManageList(_salesReps, refreshListWhenPlacedUnderManagement); V B Dim filter = New Predicate(Of Employee)(Function( _ anEmployee As Employee) anEmployee.City = "London") _employeeEntityListManager = New EntityListManager(Of Employee)(_em1, filter, Nothing) Dim refreshListWhenPlacedUnderManagement As Boolean = True _employeeEntityListManager.ManageList(_salesReps, refreshListWhenPlacedUnderManagement ) This code sets up an EntityListManager to watch the cache for changes to Employees, or the insertion of new Employees. If any changed or new Employee is found to be based in London, a reference to that Employee will be added to the _salesReps list. If any Employee that was located in London is moved to another city, that entity will be removed from the list. The second parameter to the ManageList call above, indicates whether you want the _employeeEntityListManager to clear the list and then scan the current "entity cache" and repopulate it based on the specified filter. A 'false' value would indicate that you want the list to be monitored from here on, but that you are confident of its initial population. The only requirements for the list being managed, in this case: _salesReps, are that it implement System.Collections.IList; and contain instances of IdeaBlade.EntityModel.Entity. A single EntityListManager can manage as many different lists as you wish. To put _employeeEntityListManager in charge of additional lists, you would simply invoke its ManageList method again for each desired list: C# _employeeEntityListManager.ManageList(_telecommuters, false); _employeeEntityListManager.ManageList(_fieldAgents, false); VB _employeeEntityListManager.ManageList(_telecommuters, False) _employeeEntityListManager.ManageList(_fieldAgents, False) Of course, it only makes sense to do this when the same inclusion criteria apply to each targeted list. Note that the EntityListManager is NOT 'watching' the list itself, it is only watching the EntityManager associated with the list and will insure that any changes to any entities within the EntityManager result is the addition or removal of entities from the list based on the specified filter. This means that any changes that are made to the list directly do not result in any filtering action. So as a general rule, you should usually avoid modifying a 'managed' list directly, and instead rely on its ability to stay synchronized with its associated EntityManager. The reason that the list does not 'watch' for changes to itself is that the IList interface does not provide any eventing mechanism that would allow an external component, such as the EntityListManager, to perform such a 'watch'. While some implementations of IList do offer such eventing we did not want to restrict the use of the EntityListManager working only with such lists. EntityListManagers and the NullEntity One exception to the general rule described above occurs when you want to add a NullEntity to a "managed" list. NullEntities are a special form of "detached" entities and do not reside in the cache, so there is no way that an EntityListManager will ever find one to either add to or be removed from a managed list. If you want a NullEntity in a managed list, you should manually add it. The ListManager will not remove it. EntityListManagers and duplicates The EntityListManager will not eliminate duplicates from a list. It will, however, insure that it does not add the same entity more than once. For example, suppose you direct the following statement against a list, _salesReps, that is already being managed to include Employees based in London: C# _salesReps.AddRange(_entityManager.Employees.Where(e=>e.City == "London")); VB _salesReps.AddRange(_entityManager.Employees.Where(e=>e.City == "London")) You will end up with duplicate references to each of the London employees! Again, the general rule is that if you have a managed list, it is best not to attempt to populate it directly. Allow the EntityListManager to handle the work. EntityListManagers and performance EntityListManagers do create a certain amount of overhead, so be judicious in their use. It is also possible to narrow their scope of what they must monitor more than we did in our examples above. We instantiated our EntityListManager as follows: C# var filter = new Predicate<Employee>( delegate(Employee anEmployee) { return anEmployee.City == "London"; }); EntityListManager<Employee> employeeEntityListManager = new EntityListManager<Employee>(_em1, filter, null); VB Dim filter = New Predicate(Of Employee)(Function(anEmployee _ As Employee) anEmployee.City = "London") Dim employeeEntityListManager As New EntityListManager( _ Of Employee)(_em1, Filter, Nothing) The third argument, which we left null, is an array of EntityProperty objects. By leaving it null, we told the manager to submit any added or modified Employee to the test encoded in the filter Predicate. Suppose that, instead, we pass a list of properties of the Employee to this argument: C# EntityListManager<Employee> employeeEntityListManager = new EntityListManager<Employee>(_entityManager, filter, new EntityProperty[] { Employee.CityEntityProperty }); VB Dim employeeEntityListManager As New EntityListManager( _ Of Employee)(_em1, filter, New EntityProperty() {Employee.CityEntityProperty}) Now the EntityListManager will apply its test (about City being equal to London) only to an Employee whose City property, specifically, was modified. If you simply change only the Birthdate of an Employee already in the cache, the rule will not be evaluated. It can, after all, be safely assumed that said Employee would already be in the lists being managed if the value in its City property were London. Coding more involved rules In some of the examples above we passed an anonymous delegate to the constructor of the Predicate filter. Thats great for simple rules, but you can declare the predicate separately if you need to do something more involved. This also gives you a chance to name the rule, which can make your code more readable. Heres a simple example: C# private void SetUpEntityListManagerWithNamedDelegate() { // Identify Customer currently being edited by some process; // this is a stand-in. _currentCustomer = _em1.Customers.FirstOrNullEntity(); EntityListManager<Order> orderEntityListManager = new EntityListManager<Order>(_em1, FilterOrdersByDate, new EntityProperty[] { Order.PropertyMetadata.OrderDate, Order.PropertyMetadata.Customer } ); }
/// <summary> /// This rule gets the 1996 Orders for the current Customer /// </summary> /// <param name="pOrder"></param> /// <returns></returns> Boolean FilterOrdersByDate(Order pOrder) { return (pOrder.OrderDate.Value.Year == 1996 && pOrder.Customer == _currentCustomer); } VB Private Sub SetUpEntityListManagerWithNamedDelegate() ' Identify Customer currently being edited by some process; ' this is a stand-in. _currentCustomer = _em1.Customers.FirstOrNullEntity()
Dim orderEntityListManager As New EntityListManager(Of Order)(_em1, _ AddressOf FilterOrdersByDate, New EntityProperty() { _ Order.PropertyMetadata.OrderDate, Order.PropertyMetadata.Customer}) End Sub
''' <summary> ''' This rule gets the 1996 Orders for the current Customer ''' </summary> ''' <param name="pOrder"></param> ''' <returns></returns> Private Function FilterOrdersByDate(ByVal pOrder As Order) As Boolean Return (pOrder.OrderDate.Value.Year = _ 1996 AndAlso pOrder.Customer.Equals(_currentCustomer)) End Function
Import entities Occasionally, you may have a secondary workflow or editing context that needs to be isolated from the primary workflow. Creating another EntityManager and importing entities is a great way to create a "sandbox" environment that copies and isolates changes to the entities until you are ready to merge them back into the primary workflow. The secondary EntityManager likely needs to be seeded with entities from the primary EntityManager. If they have already been cached, this avoids having to go back to the datasource to retrieve them, but more importantly, some of the entities may be modified and you want their modified state to be copied into the secondary manager. Import entities into an EntityManager To import entities into another EntityManager call ImportEntities. C# manager.ImportEntities(entities, MergeStrategy.PreserveChanges); VB manager.ImportEntities(entities, MergeStrategy.PreserveChanges) This creates a copy of the entities into the cache of the EntityManager. Any changes to the copied entities in this cache will not affect the source entities. Notice that the second parameter of ImportEntities takes a MergeStrategy. This MergeStrategy determines how the entities are merged if they already exist in the target EntityManager's cache. If you are importing entities into an empty EntityManager, then you don't need to worry about merge conflicts. This only occurs when you are merging into an EntityManager which may have potentially modified versions of the same entities. For an in depth discussion about merging entities see Merge query results into the entity cache. Work with multiple EntityManagers Remember that when working with multiple EntityManagers that each one is a separate editing context and that you may (and probably will) have multiple copies of the same entities (entities that have the same ID or primary key). When you are binding the entities to the UI, make sure you understand which instance of the entity you are binding to. Be very clear about which EntityManager you are using to initialize a view, and avoid mixing entities from different EntityManagers in the same view. To learn more about working with multiple EntityManagers see Multiple EntityManagers. Find entities to import If you don't have an easy reference to all the entities you want to import, FindEntityGraph can be of great assistance. FindEntityGraph starts with a set of root entities and retrieves all related entities for the specified associations (EntitySpans ). FindEntityGraph does not query the datasource, so make sure that you already have the entities in cache by using the Include operator in the original query. C# var rootEntities = new List<Entity>(); rootEntities.Add(someEmployees);
var spans = new List<EntitySpan>();
EntitySpan span = new EntitySpan(typeof(Employee), EntityRelations.FK_Order_Employee, EntityRelations.FK_OrderDetail_Order, EntityRelations.FK_OrderDetail_Product);
spans.Add(span);
var entityGraph = manager.FindEntityGraph(rootEntities, spans, EntityState.AllButDetached); VB Dim rootEntities = New List(Of Entity)() rootEntities.Add(someEmployees)
Dim spans = New List(Of EntitySpan)()
Dim span As New EntitySpan(GetType(Employee), _ EntityRelations.FK_Order_Employee, _ EntityRelations.FK_OrderDetail_Order, _ EntityRelations.FK_OrderDetail_Product)
spans.Add(span)
Dim entityGraph = manager.FindEntityGraph(rootEntities, spans, EntityState.AllButDetached) Temporary id remapping During an import, any entities with a temporary id will be assigned a new temporary id to avoid conflicts with temporary ids that already exist in the cache. All foreign key references will also be remapped, so that object navigation will continue to work. Generally, you do not need to worry about this, but if you are inspecting the temp ids, this is why they change after import. Inspect original values You can inspect the original version of a data property value even if the entity is in a modified or deleted state. Normally when you read an entity data property, DevForce returns the "current" value. Internally DevForce maintains several versions of each data property's value: the current version (with a pending change if any), its original version (the value as it was when the entity was last retrieved or saved), and a proposed version (for a change that is no yet recorded). This topic covers the EntityVersion enumeration and how you use it in combination with the GetValuemethod to inspect specific versions of property data.
Data Property Versioning When you undo changes you've made to a modified entity, DevForce reverts the entity's data properties to their "original" values. DevForce maintains more than one value per data property. In fact, it may hold up to three versions of the property value : the current, the original, and a proposed version. The Current version is the one you see most of the time. You won't often want to look at other versions. You'll be happy to know the original is there somewhere, standing by in case you "undo". But there may come a time, perhaps while writing validation logic that concerns transitions from one state to another, when you want to examine the other versions. This topic explains the versions in some detail and shows how you can inspect them. DevForce only versions "data properties", the properties that you persist to the database. It doesn't version navigation properties nor the custom properties that you wrote. Seeing is believing This is easier to talk about with an example. Let's write a simple statement using the CompanyName property of an umodified Customer object: C# originalName = testCustomer.CompanyName; VB originalName = testCustomer.CompanyName The "originalName" variable is assigned the Current version of the CompanyName property. Technically, the CompanyName property returns the Default version which might be other than the Current under different circumstances. See below. Because this entity is unmodified, the Current version is the same as the Original version which is why we called the variable, originalName. Let's change the Current version: C# testCustomer.CompanyName = newName = "New Name"; VB testCustomer.CompanyName = newName = "New Name" The current CompanyName will be newName; the original will remain originalName. To prove it, we'll use the EntityAspect to go under the hood. EntityAspect has a GetValue method with an overload that accepts one of the EntityVersion enums. C# // Get the current and original values var curVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current); var origVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original);
Assert.AreEqual(newName, curVal); Assert.AreEqual(originalName, origVal); Assert.AreNotEqual(curVal, origVal); // driving the point home. VB ' Get the current and original values Dim curVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current)) Dim origVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original))
Assert.AreEqual(newName, curVal) Assert.AreEqual(originalName, origVal) Assert.AreNotEqual(curVal, origVal) ' driving the point home. Let's detach the entity and test the versions again: C# // Hold onto its current manager var manager = testCustomer.EntityAspect.EntityManager;
// Get the current and original values again curVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current); origVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original);
// Both values are available, even in the detached state Assert.AreEqual(newName, curVal); Assert.AreEqual(originalName, origVal); VB ' Hold onto its current manager Dim manager = testCustomer.EntityAspect.EntityManager
' Get the current and original values again curVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current)) origVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original))
' Both values are available, even in the detached state Assert.AreEqual(newName, curVal) Assert.AreEqual(originalName, origVal) DevForce entities are "self-tracking". They retain both their current and original values even when detached from an EntityManager. We can serialize them, deserialize them, hand them around, import them in another EntityManager, or re-attach them to their former manager like this: C# // Re-attach **and** ensure its //[[EntityState>>EntityState]]// is "modified" manager.AttachEntity(testCustomer); testCustomer.EntityAspect.SetModified();
// Get the current and original values again curVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current); origVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original);
// We've restored the modified entity Assert.IsTrue(testCustomer.EntityAspect.EntityState.IsModified()); Assert.AreEqual(newName, curVal); Assert.AreEqual(originalName, origVal); VB ' Re-attach **and** ensure its //[[EntityState>>EntityState]]// is "modified" manager.AttachEntity(testCustomer) testCustomer.EntityAspect.SetModified()
' Get the current and original values again curVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current)) origVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original))
' We've restored the modified entity Assert.IsTrue(testCustomer.EntityAspect.EntityState.IsModified()) Assert.AreEqual(newName, curVal) Assert.AreEqual(originalName, origVal) Finally let's undo the pending changes and check the values again: C# testCustomer.EntityAspect.RejectChanges(); // undo
// Get the current and original values again curVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current); origVal = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original);
// Back to the initial unmodified entity Assert.IsTrue(testCustomer.EntityAspect.EntityState.IsUnchanged()); Assert.AreEqual(originalName, curVal); // current restored to original Assert.AreEqual(originalName, origVal); Assert.AreEqual(curVal, origVal); // driving the point home. VB testCustomer.EntityAspect.RejectChanges() ' undo
' Get the current and original values again curVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Current)) origVal = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Original))
' Back to the initial unmodified entity Assert.IsTrue(testCustomer.EntityAspect.EntityState.IsUnchanged()) Assert.AreEqual(originalName, curVal) ' current restored to original Assert.AreEqual(originalName, origVal) Assert.AreEqual(curVal, origVal) ' driving the point home. The Original version The Original value is the value retrieved from the database. Newly added entities don't have an Original value. They have a Current value. They get an Original value when you save. You can simulate a save by calling EntityAspect.AcceptChanges. You should be wary of doing that although it can be useful in tests. The Proposed version DevForce entities implement System.ComponentModel.I EditableObject. That means they have an additional level of do-undo. IEditableObject has three methods: Methods Summary BeginEdit() Begin editing the object. An edit session is open. While open, changes go into the Proposed version. CancelEdit() Cancel the edit. The edit session closes and the Proposed versions are discarded. EndEdit() Ends the edit session and pushes the Proposed version values into the Current version. You can inquire about the Proposed version any time. C# var proposed = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Proposed); VB
Dim proposed = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Proposed) The Default There is a "Default" EntityVersion enum. Default is not really a version. Rather, it maps to the version whose value would be returned by calling the property directly as we see in this example: C# var value = testCustomer.CompanyName; var default = (string) testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Default); Assert.AreEqual(value, default ); // Always true. VB Dim value = testCustomer.CompanyName Dim default1 = CStr(testCustomer.EntityAspect.GetValue("CompanyName", EntityVersion.Default)) Assert.AreEqual(value, default1) ' Always true. That's a tautology. You want to know how the Default is mapped. Default maps to ... when ... Current the entity is added, modified, unchanged, or detached. Original the entity is deleted. Proposed the entity is in the middle of an IEditableObject editing session. Asking for a version that isn't defined What if you ask for the Original of an added entity? An added entity doesn't have an Original value. Rather than throw an exception or return a strange value, DevForce returns the Default version. DevForce always returns the Default version if the version asked for is undefined. To give another example, the Proposed version is undefined unless the entity is in an open IEditableObject edit session. If there is no open edit session and you ask for the Proposed value, you get ... the Default value. The EntityVersion Enum EntityVersion is an enum whose values you should now appreciate. Version Summary Current The value that would be saved to the database. Original The value as it was when the entity was last queried or saved. Proposed A changed value within an open IEditableObject session. It becomes the current value when the session is closed by a call to EndEdit. Default Always the same as the property value. It is usually Current; it is Proposed during an IEditableObject edit session and Original for a Deleted entity. EntityVersion is a flag enumeration, meaning you can 'OR' the values together to form a composite EntityVersion value for search purposes. Take a snapshot Last modified on March 23, 2011 22:59 You can take a snapshot of the entities in the entity cache and hold that snapshot as a potential rollback or restore point. CacheStateManager To work with the cache, use the EntityManager's CacheStateManager. Calling GetCacheState returns you an EntityCacheState that is a snapshot of the current state of the cache. You can hold on to multiple EntityCacheStates and if you need to rollback to one of them, just call RestoreCacheState. You can also save the entity cache to a stream or file. See Save cache locally on how to do this.
Validate Last modified on March 23, 2011 10:47 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 customers 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 users role). User input validation gets most of the attention but we need to validate programmatic inputs as well. That delivery date could as easily be set by business logic or a web service request as it is by a wayward click on a calendar control. The rules are the same for everyone, human and machine. Validation is hard to do well especially as the application grows and validation criteria change. Common failings include: Missing and incorrect validity checks 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. Validation concepts The validation infrastructure provided by DevForce is made up of a collection of interoperating validation components that are both easy to use and capable of handling sophisticated scenarios. The developer can:
Capabilities
Generate validity checking into business objects automatically via the DevForce Object Mapper. 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. Validate any kind of object, not just objects that derive from base business classes. Trigger validity checking at any time such as upon display, before save, or when setting properties. The engine can fire pre-set to block critically errant data from entering the object or fire post-set to accommodate temporarily invalid values. The UI can inspect the engine for rules of interest, fire them, and adjust the display accordingly. It could color a text box, for example, or hide a portion of the form until applicable criteria were met. Display a localized message in the UI without special programming. The UI could display just the validation failed message but it might also show warnings or ok messages and it might supplement the message be re-directing the application focus to the offending object and property. Each rule returns a rich, extensible object with all the information necessary for the developer to deliver a helpful response. Discover rules in the code or retrieve them at runtime from a central store. The engine automatically discovers rules in the code and can acquire rules defined externally in configuration XML, a database, or some other store of rules. The application can inspect, add, and remove rules at any time. Leverage rules inheritance. Rules defined in base classes propagate to their derived classes where they are inherited or overridden. Adjust validation behavior based on a custom validation context. The developer must have the flexibility to convey custom information to the validation process to cope with the variety of situational factors that arise in real applications. Inspect and intervene as the engine validates. The application can monitor the engines progress and interrupt, modify, or terminate a validation run at any point. Verification vs.validation The DevForce validation mechanism is called verification and all of its components are named with some variation on this word. We mean to try neither your patience nor your vocabulary. We would call our offering validation if we could. However, Microsoft uses the term validation throughout .NET. It appears in Windows Presentation Foundation (WPF) and Windows Workflow Foundation (WWF) namespaces and in the Enterprise Library as well. Microsoft also uses the following class names: ValidationError, ValidationErrorCollection, ValidationManager, ValidationResult, ValidationRule, ValidationStatus, ValidationType IdeaBlade is integrating DevForce with Microsofts 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. What about WPF and Silverlight Validation? We cant leave this digression without a parting comment about validation in Microsofts Windows Presentation Foundation. WPF validation concentrates on presentation of validation results within a WPF user interface. This is a vital aspect of any validation strategy. At present, most applications punish the user for the developers own design failings. We need better UIs and better means to guide users rather than humiliate them. DevForces verification concentrates on the validation process. It complements WPF by producing the rich validation results necessary to deliver an effective user experience. We will address the integration of these mechanisms in a separate document. Overview of validation mechanics All validation in DevForce is performed by a VerifierEngine. Every EntityManager contains its own instance of a VerifierEngine, accessible via its VerifierEngine property. VerifierEngines can also be created and used independently. The VerifierEngine class is discussed in more detail elsewhere, but the critical idea here is that every VerifierEngine contains a list of verifier instances. A verifier instance is an instance of some subclass of the Verifier class. The VerifierEngine offers a set of methods that allow collections of these verifier instances to be evaluated sequentially against an instance of a .NET class. This is the process of "performing a validation". The object to be validated can be a DevForce entity but it doesnt have to be. The object can be of any concrete type. Each verifier execution produces a VerifierResult. The engine accumulates these results in a VerifierResultCollection as it proceeds and returns the entire collection as its own result.
Adding Verifiers to a VerifierEngine Verifiers can be added to a VerifierEngine in two ways: The engine can discover them automatically by inspecting the .NET types for verifier attributes. The developer can add them programmatically. The application can combine these methods. Validation discovery Whenever a VerifierEngine is asked to "validate" a type, its first step is to discover all of the verifiers that are applicable to that type. Some of these verifiers are defined using property level attributes that may be applied to the type being validated; but verifiers may also be defined in .NET code, and even in XML. The VerifierEngine discovers all of these verifiers, and creates instances of each in an internal collection. These instances will be used to perform the actual validations. Validation using attributes Most verifier instances are responsible for the validation a single property on a single target type. Validations of this form are usually specified by marking up the target type's properties with a variety of validation attributes.
These validation attributes can either have been automatically generated on the type by DevForce or may have been added directly to the class by the developer ( usually via a metadata buddy class). Many of the most basic "database constraints" that arise from the process of using the Entity Data Model Designer to map a class will automatically appear in the generated code as validation attributes on various properties. These will appear as StringLength or RequiredValue attributes. Validation configuration Each verifier instance has its own properties which tell the VerifierEngine the conditions under which it is applicable. For example, you can define a verifier so that it runs before a proposed new property value is pushed into the business object; or after; or even both (though that is unusual). You also want most verifiers to run whenever an entire instance of a type is being validated. To specify these things, you specify the ExecutionModes on an instance of the VerifierOptions type. Performing a validation The VerifierEngine has several overloads available to actually cause a validation to occur. The methods can be called directly, but they will also be called automatically by the DevForce infrastructure at the following points: Whenever any entity within an EntityManager has any of its property values changed. In this case the EntityManager sees the change to the entity and internally calls its VerifierEngine to perform the validation. Whenever an entity is saved, the EntityServer "validates" every entity before it is saved. If any do not validate, the save is canceled. Different kinds of validation These are two different kinds of validation. In the case of a change to a property value, we really only want to perform the minimum number of validations that are relevant to the property being changed. This is called a property validation and there are specific methods on the EntityManager that are intended for this purpose. In the case of a save, we really want to "validate" the entire entity. This will mean "validating" every property along with any validations that cross properties or possibly even involve related objects. This is referred to as an instance validation. Again there is a specific method on the VerifierEngine for this purpose. Result of a validation Regardless of which form of validation is performed the result of a validation is always a VerifierResultsCollection which as the name suggests, is a collection of VerifierResults. Each VerifierResult contains a reference to the object being validated, the validation instance used to perform the validation and most importantly the "result" of the validation. VerifierResults that represent "errors" are automatically added to each entities EntityAspect.ValidationErrors collection.
Generate automatic validation attributes The easiest point of entry to DevForce validation is through the DevForce extensions to the Entity Data Model Designer. The idea is that we can get a certain set of automatic validation code generated for us simply as a side effect of setting up our domain model. This page details how to use Visual Studio Entity Data Model Designer to automatically generate validation attributes.
Validation-related code generation options 1. Open an Entity Data Model in the Visual Studio Entity Data Model Designer. 2. Display the Properties panel and then click in white space in the designer window to display the properties of the ConceptualEntityModel. In the DevForce Code Generation section, note the property Validation Attribute Mode:
3. You can choose to have DevForce generate DevForce-style validation attributes for entities and their properties, or .NET-style. We recommend that you use use DevForceVerification unless you have a compelling reason to do otherwise (e.g., you have a large amount of code that already uses the .NET attributes and facilities). DevForce verification provides a superset of the capabilities provided by .NET validation. Generated property code Here well show you the results of the three different values for Validation Attribute Mode: Generate DevForceVerification Attributes
Here is the FirstName property of an Employee object as generated with the settings shown above: C# #region FirstName property /// <summary>Gets or sets the FirstName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="FirstName", AutoGenerateField=true)] [IbVal.StringLengthVerifier(MaxValue=30, IsRequired=true, ErrorMessageResourceName="Employee_FirstName")] [DataMember] public string FirstName { get { return PropertyMetadata.FirstName.GetValue(this); } set { PropertyMetadata.FirstName.SetValue(this, value); } } #endregion FirstName property VB #Region "FirstName property"
''' <summary>Gets or sets the FirstName. </summary> <Bindable(True, BindingDirection.TwoWay), Editable(True), _ Display(Name:="FirstName", AutoGenerateField:=True), _ IbVal.StringLengthVerifier(MaxValue:=30, IsRequired:=True, _ ErrorMessageResourceName:="Employee_FirstName"), DataMember()> _ Public Property FirstName() As String Get Return PropertyMetadata.FirstName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.FirstName.SetValue(Me, value) End Set End Property #End Region ' FirstName property IbVal is an alias for the IdeaBlade.Validation namespace, defined at the top of the code file. The IbVal.StringLengthVerifier sets a maximum length on the (text) value, and its IsRequired argument declares the property non-nullable. Generate .NET validation attributes
Here is the generated code that the above settings in the EDM designer: C# #region FirstName property
/// <summary>Gets or sets the FirstName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name = "FirstName", AutoGenerateField = true)] [Required()] [StringLength(30)] [DataMember] public string FirstName { get { return PropertyMetadata.FirstName.GetValue(this); } set { PropertyMetadata.FirstName.SetValue(this, value); } } #endregion FirstName property VB #Region "FirstName property"
''' <summary>Gets or sets the FirstName. </summary> <Bindable(True, BindingDirection.TwoWay), Editable(True), _ Display(Name:="FirstName", AutoGenerateField:=True), _ Required(), _ StringLength(30), _ DataMember()> _ Public Property FirstName() As String Get Return PropertyMetadata.FirstName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.FirstName.SetValue(Me, value) End Set End Property #End Region ' FirstName property This time the non-nullability (i.e., Required) and string length constraints are specified using the .NET validation attributes. Generate No Verification or Validation Attributes
This setting results in the absence of validation-related attributes of any sort: C# #region FirstName property
/// <summary>Gets or sets the FirstName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name = "FirstName", AutoGenerateField = true)] [DataMember] public string FirstName { get { return PropertyMetadata.FirstName.GetValue(this); } set { PropertyMetadata.FirstName.SetValue(this, value); } } #endregion FirstName property VB #Region "FirstName property"
''' <summary>Gets or sets the FirstName. </summary> <Bindable(True, BindingDirection.TwoWay), Editable(True), _ Display(Name:="FirstName", AutoGenerateField:=True), DataMember()> _ Public Property FirstName() As String Get Return PropertyMetadata.FirstName.GetValue(Me) End Get Set(ByVal value As String) PropertyMetadata.FirstName.SetValue(Me, value) End Set End Property #End Region ' FirstName property
Add custom validation attributes Last modified on May 23, 2011 17:11 Default code generation of your entity model will automatically add some validation attributes to your entity properties. You can also have code generation generate custom validation attributes onto your entity properties. These following verifier attributes are automatically added to the code that DevForce generates based on the property attributes defined in the Entity Framework Designer: if a property is non-nullable the RequiredValueVerifier is added if a property has a backing column with a maximum string length, a StringLengthVerifier is added. These attributes appear as follows in the generated code: C# [IbVal.RequiredValueVerifier( ErrorMessageResourceName="Employee_Id")] public long Id { ... }
[IbVal.StringLengthVerifier(MaxValue=30, IsRequired=true, ErrorMessageResourceName="Employee_LastName")] public string LastName { ... } VB <IbVal.RequiredValueVerifier(ErrorMessageResourceName:="Employee_Id")> _ Public ReadOnly Property Id() As Long ... End Property <IbVal.StringLengthVerifier(MaxValue:=30, IsRequired:=True, ErrorMessageResourceName:="Employee_LastName")> _ Public ReadOnly Property LastName() As String To add additional verifiers to your entities, you can programmatically add verifiers, as we've seen earlier. But you have another option too: you can use a "buddy" class containing metadata to be applied to entity properties. In the buddy class you define additional validation attributes for your entity properties, and "link" the buddy class to the entity class by placing the MetadataType attribute on the class. Here's an example to make this clearer: C# [MetadataType(typeof(EmployeeMetadata))] public partial class Employee { ... }
public class EmployeeMetadata {
[RequiredValueVerifier()] public static string City;
[DateTimeRangeVerifier(MinValue="1/1/2000", MaxValue="12/31/2010")] public static DateTime? HireDate;
[RegexVerifier("Home phone", "USPhone")] public static string HomePhone; } VB <MetadataType(GetType(EmployeeMetadata))> _ Public Partial Class Employee End Class
Public Class EmployeeMetadata
<RequiredValueVerifier> _ Public Shared City As String
<DateTimeRangeVerifier(MinValue := "1/1/2000", MaxValue := "12/31/2010")> _ Public Shared HireDate As System.Nullable(Of DateTime)
<RegexVerifier("Home phone", "USPhone")> _ Public Shared HomePhone As String End Class The verifier attributes defined in the buddy class will take precedence and override the same verifier attribute for the same property in the generated code. For example, if you have a generated Employee.LastName StringLengthVerifierAttribute with IsRequired = true, and you create an Employee buddy class and add a StringLengthVerifierAttribute with IsRequired = false to the LastName property, then the LastName will no longer be required. The precedence described above will only take place for standard DevForce verifier attributes. If you have created your own custom verifier attribute, and define it in the buddy class, code generation will still discover it. But the existing generated DevForce verifier attributes will not be overridden. The verifiers may be a bit contrived here (unless you really do want to hire only US employees in this decade), but should give you an idea of what you can do. Any of the DevForce validation attributes, such as any of the range verifiers Int32RangeVerifierAttribute, Int64RangeVerifierAttribute, DecimalRangeVerifierAttribute, DoubleRangeVerifierAttribute, DateTimeRangeVerifierAttribute, etc.) or RegexVerifierAttribute, or any custom validation attributes you write which extend a DevForce verifier attribute, can be used. There are a few rules to follow, to ensure that your attributes are found: Be sure to add the MetadataType attribute to the class you will be providing metadata for. Here we've decorated the Employee class with the attribute. The buddy class can be standalone or nested, and should be public or internal, or private if used only in Desktop applications. The buddy class should define public fields or properties having the same name as the property in the class for which it's defined. These members can be either static or defined on the buddy class instance. In the case above, the Employee class should have properties named "City", "HireDate", and "HomePhone". The MetadataType attribute is defined in IdeaBlade.Core.ComponentModel.DataAnnotations for Silverlight applications, and in System.ComponentModel.DataAnnotations for desktop applications. The attribute functions the same in either environment. DevForce will look for the MetadataType attribute as it is generating code for your entities. Always remember to regenerate your models to ensure these new attributes are discovered and included in the generated code.
Consider predefined verifiers Last modified on March 22, 2011 15:18 Contents DevForce provides a collection of predefined verifiers that can either be used as is, or subclassed to add additional behaviours.
All of the verifiers listed below, with the exception of the DelegateVerifier<T>, are subclasses of the abstract base class PropertyValueVerifier. Verifier Description RequiredValueVerifier A verifier that checks that a null value has not been returned. StringLengthVerifier A verifier that checks that a string value's length is within a range. RegexVerifier A verifier that checks that a string value matches a specified regular expression. RangeVerifier<T> The abstract base class for all other RangeVerifiers. This class and all of its subclasses provide validation that a value is within a specified range. If one of the provided subclasses does not match the type of value that you need for a range validation then subclass this type. Int32RangeVerifier A RangeVerifier for Int32 values. Int64RangeVerifier A RangeVerifier for Int64 values. DoubleRangeVerifier A RangeVerifier for Double values. DecimalRangeVerifier A RangeVerifier for Decimal values. DateTimeRangeVerifier A RangeVerifier for DateTime values. ListVerifier A verifier that checks if a value is among those in a specified list. PropertyValueVerifier<T> An abstract PropertyValueVerifier that is strongly typed as to the type of parent object being validated. (Not the type of the value being validated). May be used to create strongly classed subclasses of the PropertyValueVerifier. DelegateVerifier<T> The abstract base class for all custom validations that cannot be written by subclassing one of the other verifiers provided above. Create a custom verifier DevForce ships with a number of pre-defined verifiers that can be customized in a variety of ways: examples include the DateTimeRangeVerifier, the RegexVerifier, 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. There are several ways to create a verifier, but in general the easiest ways involve starting with an existing one and modifying it to suit.
Creating a customized version of a standard (provided) verifier The method GetHireDateRangeVerifier below illustrates the creation of a 'customized' verifier by simply creating one of the standard DevForce verifiers with specific parameters, in this case, the DateTimeRangeVerifier. Other provided verifiers include many others that are specific to .NET simple types, including DecimalRangeVerifier, DoubleRangeVerifier, Int32RangeVerifier, and so forth. There is a ListVerifier, a Regex Verifier, a NamedRegexPattern verifier, and a number of others. All are defined in the IdeaBlade.Validation namespace. The constructor for the DateTimeRangeVerifier provides for all of the configuration we need. It permits us to specify the target business object type for the rule, the property on that type whose setting will trigger the running of the rule, whether a non-null value is required when that property is set, and the dates that define the valid range: C# private static Verifier GetHireDateRangeVerifier(DateTime minHireDate, DateTime maxHireDate) { Verifier v = new DateTimeRangeVerifier( typeof(Employee), // Type of the object being verified Employee.PropertyMetadata.HireDate.Name, // Property trigger false, // Non-null value is not required minHireDate, true, // starting min date (inclusive) maxHireDate, false); // ending max date (exclusive) return v; } VB Private Shared Function GetHireDateRangeVerifier( _ minHireDate as DateTime, maxHireDate as DateTime) As Verifier Dim v As Verifier = New DateTimeRangeVerifier( _ GetType(Employee), _ Employee.PropertyMetadata.HireDate.Name, _ False, _ minHireDate, True, _ maxHireDate, False) Return v End Function If you need to improve upon the default message produced by the above verifier, you can take control of the messaging by setting the verifiers VerifierArgs.ErrorMessageInfo.Error. For example, if you insert the following statement right before the return statement in the above method, your message will be the one users see: C# v.VerifierArgs.ErrorMessageInfo.ErrorMessage = String.Format( "The HireDate must fall on or after the Company's launch date of {0} " + "and before one month from today ({1}).", minHireDate.ToShortDateString(), maxHireDate.ToShortDateString()); VB v.VerifierArgs.ErrorMessageInfo.ErrorMessage = String.Format( _ "The HireDate must fall on or after the Company's launch date of {0} " _ & "and before one month from today ({1}).", _ minHireDate.ToShortDateString(), maxHireDate.ToShortDateString()) Subclassing a pre-defined verifier Another way to create a custom verifier is to subclass one of the pre-defined verifiers that are in the IdeaBlade.Validation namespace. In the following code samples below, we will create a custom verifier that checks that a given GUID property does NOT equal Guid.Empty. We will do this by subclassing a PropertyValueVerifier to define a custom class called NotEmptyGuidVerifier. Step 1: Create the custom NotEmptyGuidVerifier class by inheriting it from the PropertyValueVerifier class. The constructor takes 2 parameters, an entityType and a propertyName. This allows us to pass any entity and any Guid property. C# public class NotEmptyGuidVerifier : PropertyValueVerifier {
protected override VerifierResult VerifyValue(object itemToVerify, object valueToVerify, TriggerContext triggerContext, VerifierContext verifierContext) { var aGuid = (Guid)valueToVerify; var result = (aGuid != Guid.Empty); return new VerifierResult(result); } } } VB Public Class NotEmptyGuidVerifier Inherits PropertyValueVerifier Public Sub New(ByVal entityType As Type, ByVal propertyName As String, _ Optional ByVal displayName As String = Nothing, Optional ByVal _ shouldTreatEmptyStringAsNull? As Boolean = Nothing) MyBase.New(New PropertyValueVerifierArgs(entityType, propertyName, _ False, displayName, shouldTreatEmptyStringAsNull)) End Sub Protected Overrides Function VerifyValue(ByVal itemToVerify As Object, _ ByVal valueToVerify As Object, ByVal triggerContext_Renamed As _ TriggerContext, ByVal verifierContext_Renamed As VerifierContext) _ As VerifierResult Dim aGuid = CType(valueToVerify, Guid) Dim result = (aGuid IsNot Guid.Empty) Return New VerifierResult(result) End Function End Class Step 2: Wrap the NotEmptyGuidVerifier class in a method that returns a Verifier. We will call this method "CheckForEmptyGuidVerifier". C# private static Verifier CheckForEmptyGuidVerifier(Type entityType, string propertyName) { Verifier v = new EmptyGuidVerifier(entityType, propertyName); v.VerifierArgs.ErrorMessageInfo.ErrorMessage = String.Format("{0} cannot be empty", propertyName); return v; } VB Private Shared Function CheckForEmptyGuidVerifier(ByVal entityType _ As Type, ByVal propertyName As String) As Verifier Dim v As Verifier = New EmptyGuidVerifier(entityType, propertyName) v.VerifierArgs.ErrorMessageInfo.ErrorMessage = String.Format( _ "{0} cannot be empty", propertyName) Return v End Function Step 3: Add the method to the list of Verifiers that is defined inside the VerifierProvider" class that implements the IVerifierProvider interface. Note that in this example, we are using the verifier to validate an Order.CustomerID property (presumably of type GUID). C# public class VerifierProvider : IVerifierProvider {
public IEnumerable<Verifier> GetVerifiers(object verifierProviderContext) { List<Verifier> verifiers = new List<Verifier>();
//In this example, we are passing a type Order and validating the Order.CustomerID property verifiers.Add(CheckForEmptyGUIDVerifier(typeof(Order), Order.PropertyMetadata.CustomerID.Name));
return verifiers; } } VB Public Class VerifierProvider Implements IVerifierProvider Public Function GetVerifiers(ByVal verifierProviderContext As _ Object) As IEnumerable(Of Verifier) Dim verifiers As New List(Of Verifier)()
'In this example, we are passing a type Order and validating ' the Order.CustomerID property verifiers.Add(CheckForEmptyGUIDVerifier(GetType(Order), _ Order.PropertyMetadata.CustomerID.Name)) Return verifiers End Function End Class Step 4: And finally, let's test our newly created verifier. C# public void GenericVerifierForGUIDEmpty() { var mgr = new NorthwindIBEntityManager(); var anOrder = mgr.Orders.FirstOrNullEntity(); anOrder.CustomerID = Guid.Empty;
Assert.IsTrue(anOrder.EntityAspect.ValidationErrors.Count > 0, "CustomerID is assigned to GUID.Empty"); VB Public Sub GenericVerifierForGUIDEmpty() Dim mgr = New NorthwindIBEntityManager() Dim anOrder = mgr.Orders.FirstOrNullEntity() anOrder.CustomerID = Guid.Empty
Assert.IsTrue(anOrder.EntityAspect.ValidationErrors.Count > 0, _ "CustomerID is assigned to GUID.Empty") End Sub Now lets recap what we just did. 1. We created our generic verifier by subclassing a PropertyValueVerifier class that is pre- defined in the IdeaBlade.Validation namespace. 2. We wrap up the class inside a method that returns a Verifier. This is a required signature to add the verifier to the VerifierEngine. 3. We add the method to the list of Verifiers that is defined inside a class that implements an IVerifierProvider interface. In our example, this class is called VerifierProvider. 4. Using a TestMethod, we test the Verifier by retrieving anOrder and assigning its CustomerID to Guid.Empty. The Assert.IsTrue method should succeed as anOrder.EntityAspect.ValidationErrors.Count should be more than 0. Creating a completely custom verifier using the DelegateVerifier Despite the imposing name, the DelegateVerifier is really just a completely custom verifier. With this verifier you define the type of the object to verify, when and how to apply the verifier, and the validation to be performed. The DelegateVerifier is particularly useful for verifying multiple properties on a type, and for cross-type validation. When you create the custom verifier you provide a VerifierCondition, which is really just a function which performs the actual validation wanted. The function returns a VerifierResult, indicating whether the validation succeeded or not. You can also provide the constructor with the an ApplicabilityConstraint. Another imposing term, but it's just a function which given an object and some "context", determines whether the verifier should be run, in other words, whether it's applicable. As with other verifiers, you can also set the VerifierOptions. These options control when the verifier will be executed, and what to do when a validations fails. The verifier also needs one or more "triggers", one or more members, either on the type to be verified or another type, which "trigger" validation. Usually a trigger is the setter for a property, but it can also be a method call. If the custom validation isn't actually triggered by a property set but instead only on the object as a whole, the triggers can indicate the properties of interest. Triggers Consider a constraint that specifies that the HireDate for an Employee must be later than the Employees BirthDate. That seems a pretty reasonable requirement, but suppose when the user enters a HireDate of today, this rule is found to have been violated. But the Employee really was hired today, and the problem is that the BirthDate previously entered for the Employee was in error, specifying the year 2015 when it should have said 1985. If we prevent the new HireDate value from being entered into that property, well subject the user to a lot of unnecessary work. Shell have to clear the new HireDate, even though it is entirely correct, and then go fix the BirthDate value, and then come back and re-enter the new HireDate value, even though she got it right the first time. Users dont have a lot of tolerance for this sort of thing! Its confusing, irritating, or often both. A verifier that applies to BirthDate doesnt 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 Employees 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! Example With some terminology out of the way, let's look at a simple sample. Here we'll define a custom verifier for the Employee class to require that the employee's birth date precedes her hire date. The VerifierCondition is simple: the BirthDate must fall before the HireDate. The triggers are the "BirthDate" and "HireDate" properties on the Employee, and options here tell the engine that verification should be performed after either of these properties is set, and for instance-level validation. C# private static Verifier GetBornBeforeHiredVerifier() { string description = "Must be born before hired."; DelegateVerifier<Employee> v = new DelegateVerifier<Employee>(description, BornBeforeHiredCondition);
if (pTriggerContext != null && // We are not checking the proposed value // because don't expect to call it preset pTriggerContext.Timing == TriggerTiming.BeforeSet) { throw new VerifierException( "BornBeforeHired verifier not implemented for Preset"); } return new VerifierResult(pEmp.BirthDate < pEmp.HireDate); } VB Private Shared Function GetBornBeforeHiredVerifier() As Verifier Dim description As String = "Must be born before hired." Dim v As New DelegateVerifier(Of Employee)(description, _ AddressOf BornBeforeHiredCondition)
v.VerifierOptions.ExecutionModes = _ VerifierExecutionModes.InstanceAndOnAfterSetTriggers Return v End Function
Private Shared Function BornBeforeHiredCondition(ByVal pEmp _ As Employee, ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierResult
' We are not checking the proposed value ' because don't expect to call it preset If pTriggerContext IsNot Nothing AndAlso pTriggerContext.Timing = _ TriggerTiming.BeforeSet Then Throw New VerifierException( _ "BornBeforeHired verifier not implemented for Preset") End If Return New VerifierResult(pEmp.BirthDate < pEmp.HireDate) End Function In the above code we define a DelegateVerifier that returns, as always a VerifierResult. We add two triggers to our verifier, since it tests the relative value of two different properties, and we want a change in either to result in re-application of our validity test. BeforeSet Versus AfterSet Execution and Its Interaction With ErrorNotificationMode This section requires an understanding of the use of VerifierOptions to configure validation. Note that we have also the VerifierExecutionModes for this verifier set to InstanceAndOnAfterSetTriggers . This doesnt matter so much if the VerifierErrorNotificationMode is set at the default of Notify . Under that setting a verification failure will not result in an exception that blocks the commitment of the proposed value into the business object. But if, on the other hand, the VerifierErrorNotificationMode were set to ThrowException or NotifyAndThrowException then running the verifier in AfterSet rather than BeforeSet mode would be essential. To understand why, consider what would happen if a user discovered that both the HireDate and BirthDate values currently set for a given Employee were incorrect, even though valid according to the born before hired rule. To correct them, she needs to change both values; but, upon changing the BirthDate value, the verifier was executed and discovered that the new BirthDate value caused the HireDate and BirthDate values to be invalid relative to one another. The user planned to correct this problem by entering a new HireDate value which would bring the two values back into relative validity. But if the verifier triggered by the BirthDate change ran BeforeSet and threw an exception, the proposed new value would be prevented by the exception from entering the business object. The user might then try entering the new HireDate value first; but this might also cause a BeforeSet exception. So she would, in fact, be unable to correct the property values for the invalid Employee. It is therefore a good idea to set the VerifierOptions for such a cross-property verifier so that either 1) The verifier runs AfterSet. or 2) The VerifierErrorNotificationMode is set to Notify. Thus any validation failure will not throw an exception during and before the 'set' of a temporarily invalid but necessary intermediate value. Cross-Type Verifiers A cross-type verifier defines a rule whose application requires the inspection of at least one business object of a type different than the business object being validated. For example, suppose you have a rule that says the OrderDate on any Order may not be earlier than the HireDate of the Employee writing the Order. Clearly the application of this rule requires the inspection of at least one Order and one Employee. If the change that triggers the verifier is to the Employee.HireDate rather than the OrderDate of an Order, then all Orders associated with the changed Employee must be checked. Note that such a verifier needs to have multiple triggers associated with it. In the case of our example, it should be executed whenever either the HireDate of an Employee, or the OrderDate of an Order, is changed. There is another issue with such a verifier: where should it be defined? DevForce initiates its discovery process for the verifiers associated with a particular type when the first instance of that type is retrieved into the cache. So if we define our Employee.HireDate / Order.OrderDate verifier in the Employee class, it will be discovered automatically when the first Employee is retrieved during a given application session. But if an Order is retrieved and changed before any Employee is downloaded, the verifier will not be discovered and will not be run. The same issue exists, in reverse, if we choose to define the verifier in the Order class. Now if we retrieve and change an Employee before retrieving any Orders, again the verifier wont be discovered, since it is not defined on the Order type. The solution to this conundrum is to order DevForce explicitly to discover the verifiers for any type that may have verifiers involving a type other than itself. For example, to get EntityManager _em1 to discover the verifiers for the Order type, you can use the following statement: C# myEntityManager.VerifierEngine.DiscoverVerifiers(typeof(Order)); VB myEntityManager.VerifierEngine.DiscoverVerifiers(GetType(Order)) This should be done before there is any chance that one of the verifiers defined in the Order class might be needed for some other type. We'll define a DelegateVerifier for the cross-type validation to be performed in our sample. We're defining the verifier for the Order class, and then defining triggers for both the OrderDate property of Order and the HireDate property of Employee. We also define the "path" from Employee to Orders. We could have inverted this setup and defined the verifier for the Employee type; it doesn't really matter to DevForce. C# /// <summary> /// Implement IVerifierProvider interface to give /// DevForce the verifiers in use at run time. /// </summary> public class VerifierProvider : IVerifierProvider {
public IEnumerable<Verifier> GetVerifiers( object verifierProviderContext) { List<Verifier> verifiers = new List<Verifier>(); verifiers.Add(GetOrderDateAfterHiredVerifier()); return verifiers; } }
///<summary>Get the OrderDateAfterHired Verifier.</summary> private static Verifier GetOrderDateAfterHiredVerifier() {
string description = "OrderDate must be after the sales rep's HireDate."; DelegateVerifier<Order> v = new DelegateVerifier<Order>( description, OrderDateAfterHiredCondition);
v.AddTrigger(Order.PathFor(o => o.OrderDate)); v.AddTrigger(new TriggerLink(new TriggerItem (typeof(Employee), Employee.PathFor(e => e.HireDate)), e => ((Employee)e).Orders, // Path from trigger (Employee) to Order true)); // True = that path returns multiple orders return v; }
///<summary> ///Implementor of the OrderDateAfterHired Verifier condition. ///</summary> private static VerifierResult OrderDateAfterHiredCondition( Order target, TriggerContext triggerContext, VerifierContext pVerifierContext) {
if (triggerContext != null && triggerContext.Timing == TriggerTiming.BeforeSet) { throw new VerifierException( "OrderDateAfterHired verifier not implemented for Preset"); } bool isOk = target.OrderDate.HasValue && target.SalesRep.HireDate.HasValue && target.OrderDate.Value >= pTarget.SalesRep.HireDate.Value;
return new VerifierResult(isOk); } VB '''<summary>Get the OrderDateAfterHired Verifier.</summary> Private Shared Function GetOrderDateAfterHiredVerifier() As Verifier Dim description As String = _ "OrderDate must be after the sales rep's HireDate." Dim v As New DelegateVerifier(Of Order)(description, _ AddressOf OrderDateAfterHiredCondition) v.VerifierOptions.ExecutionModes = _ VerifierExecutionModes.InstanceAndOnAfterSetTriggers v.AddTrigger(Order.PathFor(Function(o) o.OrderDate)) ' Path from trigger (Employee) to Order v.AddTrigger(New TriggerLink(New TriggerItem( _ GetType(Employee), Employee.PathFor(Function(e) e.HireDate)), _ Function(e) (CType(e, Employee)).Orders, True)) _ ' True = that path returns multiple orders Return v End Function
'''<summary> '''Implementor of the OrderDateAfterHired Verifier condition. '''</summary> Private Shared Function OrderDateAfterHiredCondition(ByVal target _ As Order, ByVal triggerContext As TriggerContext, _ ByVal verifierContext As VerifierContext) As VerifierResult If triggerContext IsNot Nothing AndAlso triggerContext.Timing = _ TriggerTiming.BeforeSet Then Throw New VerifierException( _ "OrderDateAfterHired verifier not implemented for Preset") End If Dim isOk As Boolean = target.OrderDate.HasValue AndAlso _ pTarget.SalesRep.HireDate.HasValue AndAlso _ pTarget.OrderDate.Value >= target.SalesRep.HireDate.Value Return New VerifierResult(isOk) End Function Creating a custom verifier attribute for your custom verifier The easiest way to create your own custom attribute is to inherit from one of our pre-defined verifier attribute classes. Below is an example of inheriting from a PropertyValueVerifierAttribute class by extending the NotEmptyGuidVerifier above. C# [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] public class NotEmptyGuidVerifierAttribute : PropertyValueVerifierAttribute { protected override Verifier BuildVerifierCore(Type pType, String propertyName) { return new NotEmptyGuidVerifier(pType, propertyName); } } VB <AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, Inherited := False)> _ Public Class NotEmptyGuidVerifierAttribute Inherits PropertyValueVerifierAttribute Protected Overrides Function BuildVerifierCore(pType As Type, propertyName As String) As Verifier Return New NotEmptyGuidVerifier(pType, propertyName) End Function End Class You can then apply the attribute to the fields in your buddy class. C# public class CustomerMetadata { [NotEmptyGuidVerifier] public static Guid CustomerID; } VB Public Class CustomerMetadata <NotEmptyGuidVerifier> _ Public Shared CustomerID As Guid End Class
Configure a verifier Much of the work involved in configuring a verifier is performed via the VerifierOptions class. This class collects a number of settings that affect a verifiers behavior, such as when it it is run and how it handles exceptions. An instance of the type is available as a property on the VerifierEngine class via the DefaultVerifierOptions; property and also on the Verifier, VerifierArgs, and VerifierResults classes as the VerifierOptions property. VerifierOptions are "inherited" or "defaulted" from the VerifierEngine down to all of a VerifierEngine's Verifiers and VerifierArgs and finally to the VerifierResults of each Verifier. This will be described in more detail later.
Properties of the VerifierOptions class
Property Description Type System Default Value ExecutionModes Gets or sets the conditions under which which a verifier is executed. See the material immediately below the table for more information about these. Enum VerifierExecutionModes.InstanceAndOnBefor eSetTriggers ErrorNotificationMode Used to determine whether the Entity should throw errors during a property Enum VerifierErrorNotificationMode.Notify verification or raise the errors thru the INotifyDataErro rInfo interface instead. ShouldExitOnBeforeSetE rror Whether or not to perform the 'set' of a proposed value when an error occurs. Note that this setting is only applicable with 'BeforeSet' ExecutionMode s. Boolean false ErrorContinuationMode Gets or sets whether a failure in this verifier should stop the execution of the remainder of the batch in which this verifier is executing Enum VerifierErrorContinuationMode.Continue TreatWarningsAsErrors Whether to treat warnings as errors Boolean false ShouldTreatEmptyString AsNull For required value validation, this determines whether empty strings are treated as if they were null. Boolean true RawVerifierOptions A version of this same VerifierOptions instance without "inheritence" interpretation. May be used to determine which properties are actually inherited VerifierOpti ons N/A The system default values shown in the table above are the default settings for the properties of DefaultVerifierOptions. All VerifierOptions properties can be inherited from a parent class and, by default, do so. A VerifierEngine is parent to any Verifiers contained within it; A Verifier, in turn, is parent to any VerifierResults resulting from its execution. By default, every verifier is created with a VerifierOptions instance is created with every property conceptually set to inherit its vaoue from its parent as described below: All of enum-valued property types on VerifierOptions (VerifierExecutionModes, VerifierErrorNotificationMode and VerifierErrorContinuationMode) each have a special Inherit enumeration value that may be used to indicate inheritance from a parent. Properties that are of type bool? (nullable booleans) may be set to null to accomplish the same result. This means that if any property on a VerifierEngine's DefaultVerifierOptions is changed, then by default, every Verifier and VerifierResult will inherit this change. And since Verifier and VerifierResult have their own VerifierOptions property, any aspect of VerifierOptions can be overwritten ( changed from 'Inherit' to another value) for any given Verifier or VerifierResult. Any property value returned from a VerifierOption instance will be the "interpreted" value. This means that if a value is inherited from a parent you will see the parent's value ( and it's parent's value if necessary). Therefore if you set one of the VerifierOption's properties to "Inherit" and then read the value back you will get the actual inherited value. If you need to know whether a value is inherited as opposed to having been actually set on a VerifierOption's instance, the RawVerifierOptions property described below may be used. Since the VerifierEngine.DefaultVerifierOptions property is the root parent for all VerifierOptions instances, it will not allow "Inherit" to be set on any of its properties. ExecutionModes The determination of when and how a verifier is executed is controlled via the IdeaBlade.Validation.VerifierOptions.ExecutionModes and IdeaBlade.Validation.Verifier.TriggerLinks properties on each verifier. A verifier can be executed either 1. in response to a single change (e.g., of a property value); or 2. in the context of validating an entire object. The first type of validation listed above is known as a property value validation. The second is known as an instance validation. We would apply a property value validation when (for example) a user was changing, or had just changed, the HireDate on an employee. At that time we would only want to run those validation tests whose outcome we feel may have been affected by this specific change. Our goal would be to provide the end user with instant feedback about their change. In most cases, it will never be easier for them to correct a mistake than immediately after making it! Property value validations can be subdivided into before and after categories. A BeforeSet validation is applied before a proposed new property value is actually pushed into the business object. This can be used to prevent invalid data from ever getting into the business object. An AfterSet validation is applied after the new value is pushed into the business object. The ShouldExitOnBeforeSetError and ErrorNotificationMode properties described later can be used to control further details involving a property value validation. Instance validation describes an operation that completely validates an entire entity, applying all relevant validation rules, whether those rules apply to individual properties of that entity, to a combination of its properties (the validity of whose values must be assessed in relation to each other), or in some other way to the entity as a whole. We might perform an instance validation, for example, on a particular instance of an Employee before permitting it to be saved to the database. This would likely require performing validation tests on a number of individual properties of the employee object; it might also require testing the state of related objects (such as the Orders written by that Employee). Only if the entire suite of tests required to fully validate the Employee were passed would we give the okay to save it to the database. Note that instance validation would be unnecessary if we could really be sure that every change affecting our Employee would be addressed by an appropriate set of property value validations. In practice this can be very difficult, or even impossible, to ensure. What if, for example, our Employee is changed by some other application which doesnt apply the same rules that our does? What if it is changed directly in the database by someone with access to that? Even when we can guarantee that neither of those things happen, the mechanisms by which a given entity can be changed inside a single application can become quite complex over time. For all of those reasons, developers commonly perform instance validation at such key junctures as when an entity is submitted for saving. Its an important last line of defense against invalid data. It is common for a given verifier to be applicable during instance validation as well as during property value validation. Every verifier comes with information about the situations in which it should be executed, via its VerifierOptions.ExecutionModes property. That property takes values from a VerifierExecutionModes enumeration whose choices permit you to make your verifier run in any and all of the circumstances you deem appropriate. The values in the VerifierExecutionModes enumeration, and their impacts, are as follows: Enum value Description Instance Run during instance validation. OnBeforeSetTriggers Run in response to designated triggers, before the proposed value has been pushed into the business object. OnAfterSetTriggers Run in response to designated triggers, after the proposed value has been pushed into the business object. InstanceAndOnBeforeSetTriggers Run during instance validation and in response to designated triggers (usually property value changes), before the proposed value is pushed into the business object. InstanceAndOnAfterSetTriggers Run during instance validation and in response to designated triggers (usually property value changes), after the proposed value has been pushed into the business object. All Run during instance verification and in response to designated triggers (both before the proposed value is pushed into the business object, and also after it has been pushed into the business object). Disabled Do not run. Inherit Inherit the ExecutionModes setting from the types parent (Verifier if a VerifierResult, and VerifierEngine if a Verifier). Settings of InstanceAndOnBeforeSetTriggers and InstanceAndOnAfterSetTriggersare by far this most common (after Inherit). All is a particularly uncommon setting because it orders triggered execution both before and immediately after a new value is pushed into a business object. However, a verifier, as it so happens, has access to information about the triggering context in which it is being run (i.e., before or after), so it is possible to include within a single verifier logic that will only be applied in one context or another. From this arises the possibility that you, the developer, might want such a verifier to run in both triggering contexts. BeforeSet Versus AfterSet Execution This topic deserves special discussion, as the choice between these two execution modes can be difficult. All other things being equal, it is desirable never to allow invalid values into a business object in the first place. If all things were equal, one would use OnBeforeSetTriggers or InstanceAndOnBeforeSetTriggers as the ExecutionModes for all property-level verifiers. However, in practice, these settings can cause problems, especially before an application has been fully fleshed out. For example, in the user interface, a BeforeSet verifier can demand that the end user fix a bad value entered for a property right then and there, before moving on to any other work -- even if she hasnt the faintest idea what to change the bad value to. [Note that this will NOT occur where the ErrorNotificationMode is set to Notify ( the default).] In such as case, in order to handle such issues in a manner thats friendly to the end user then you may want to use VerifierExecutionModes.InstanceAndOnAfterSetTriggers. ErrorNotificationMode Regardless of whether validation is performed before or after a property is set, the next issue deals with how to handle any validation errors. The VerifierErrorNotificationMode enumeration offers the following choices: Enum value Description Notify Causes notification through the INotifyDataErrorInfo and IDataErrorInfo interfaces. ThrowException Throws an exception. NotifyAndThrowException Notify using INotifyDataErrorInfo and IDataErrorInfo interfaces and then throw an exception. Inherit Inherit the ErrorNotificatioMode from the parent. Basically, there are two choices, either make use of the INotifyDataErrorInfo/IDataErrorInfo interfaces or throw an exception. A third choice, that of doing both, is available but is rarely used. We tend to recommend going with the use of notification interfaces, although there are use cases where throwing an exception makes a good deal of sense. You can even mix and match with some verifiers set up one way and others another way. ShouldExitOnBeforeSetError In the event that one of the "BeforeSet" ErrorNotificationModes is selected AND you have an ErrorNotificatioMode of Notify, you have one further choice: Do you want the property setter to exit before actually setting the value in the case of a validation error? Note that exiting simply means that the property never gets set, but no error is thrown. This can be very handy, if you want to insure that "bad" values never make it into your entity but at the same time you really want to use notification semantics and don't want any errors thrown within your property setters. Setting this value to null (Nothing in VB) is the equivalent of setting any of the enumerated values to "Inherit". ErrorContinuationMode Within an instance validation, many individual verifiers may need to be executed in sequence. The ErrorContinuationMode allows any individual validation to stop the process. This is often desirable if we know that the failure (or success) of one validation means that we can short-circuit the need for further validations of the instance. The ErrorContinuationMode enumeration consists of the following values: Enum value Description Stop Stop any further verifier execution within this batch if an error is encountered. Continue Continue executing other verifiers in this batch even if an error is encountered (the default). Inherit Inherit the ErrorContinuationMode from the parent. TreatWarningsAsErrors The execution of any verifier results in a VerifierResult instance. Each VerifierResult has a VerifierResultCode that describes the type of the result. Setting the TreatWarningsAsErrors to true, will cause any VerifierResults with a VerifierResultCode of OkWarning to be treated as validation errors. ShouldTreatEmptyStringAsNull The ShouldTreatEmptyStringAsNull property is only applicable in the context of a string length and required value validations. The idea is to provide the developer with the ability to consider an "Empty string" as a null for the purposes of performing required value validations. This is usually a good practice, because it is very difficult for a user of an application to "see" the difference in a text box between a null value and an empty string, and having an empty string pass validation whereas a null value fails it can be very confusing. RawVerifierOptions An "uninterpreted" version of any VerifierOptions instance is available via the RawVerifierOptions property. The version returned by this property will not perform any interpretation of the property values, so you may use this version to determine whether any given property is actually inherited or not. Discover verifiers A VerifierEngine is both a container and an execution engine for verifiers. Verifier instances are added to each engine either via a discovery process or programmatically.
Verifier discovery The VerifierEngine lazily discovers verifiers in the types it is asked to verify. Note: automatic discovery is not always a good thing, and developers can disable an engines automatic discovery. An engine with automatic discovery disabled can still perform discovery when asked to do so. When a VerifierEngine attempts to verify an instance of a type it has not seen before, it probes the type reflectively, looking for verifiers defined for the type and its base types. The probing strategy is as follows. 1. Look for instances of the VerifierAttribute. DevForce provides a number of common verifiers in attribute form all of which descend from VerifierAttribute. The developer can also add custom VerifierAttribute subclasses just as he can add custom Verifiers. These define the attributed verifiers. In Silverlight applications, .NET ValidationAttributes will also be discovered. 2. Look for VerifierAttributes on the members of the metadata "buddy" class defined for the type 3. Look for any implementations of the IVerifierProvider interface, and call the GetVerifiers method of each. We have seen the attribute verifiers earlier. A VerifierProvider might look like the following: C# /// <summary> /// Implement IVerifierProvider interface to give DevForce /// the verifiers in use at run time. /// </summary> public class VerifierProvider : IVerifierProvider {
public IEnumerable<Verifier> GetVerifiers( object verifierProviderContext) { List<Verifier> verifiers = new List<Verifier>(); verifiers.Add(GetHireDateRangeVerifier()); verifiers.Add(new BirthDateRangeVerifier()); verifiers.Add(GetBornBeforeHiredVerifier()); verifiers.Add(GetPhoneNumberVerifier(Employee.PropertyMetadata.HomePhone)); return verifiers; } } VB ''' <summary> ''' Implement IVerifierProvider interface to give DevForce ''' the verifiers in use at run time. ''' </summary> Public Class VerifierProvider Implements IVerifierProvider
Public Function GetVerifiers(ByVal verifierProviderContext _ As Object) As IEnumerable(Of Verifier) Dim verifiers As New List(Of Verifier)() verifiers.Add(GetHireDateRangeVerifier()) verifiers.Add(new BirthDateRangeVerifier()) verifiers.Add(GetBornBeforeHiredVerifier()) verifiers.Add(GetPhoneNumberVerifier(Employee.PropertyMetadata.HomePhone)) Return verifiers End Function End Class The class that implements the IVerifierProvider must have an empty public constructor. The reason for this is that DevForce will internally create an instance of this type and call its GetVerifiers method. For this reason it is usually a good idea to have your IVerifierProvider implementation be a completely separate class, so that you will not be tempted to remove the public constructor for other reasons. Adding or removing a verifier programmatically Verifiers can be also added or removed from a VerifierEngine by calling its AddVerifier and RemoveVerifier methods. Verifiers can be added or removed from a VerifierEngine at any time. The engine raises a VerifiersChanged event whenever verifiers are added or removed. This event will inform any subscriber of the addition or removal of any verifier. Note that the event is also raised when triggers are added or removed from a verifier that has previously been registered in the engine. Perform a validation In most cases, you will want to perform validation either when a property on one of your entities is set, or as part of a comprehensive validation of an object instance.
Property level validation
Property level validations should be run whenever the value of some property on an entity is set and that property has a verifier or verifiers associated with it. For a DevForce entity, this occurs automatically because internally DevForce calls either VerifierEngine.ExecuteBeforeSet or VerifierEngine.ExecuteAfterSet whenever a property is set. Which one is called is determined by the VerifierExecutionModes settings on each verifier. This occurs during the execution of the autogenerated call to the EntityProperty.SetValue method; an autogenerated code fragment is shown below: C# ... public string CompanyName { get { ... } set { PropertyMetadata.CompanyName.SetValue(this, value); } } VB Public Property CompanyName() As String Get ... End Get Set(ByVal value As String) PropertyMetadata.CompanyName.SetValue(Me, value) End Set End Property This means that you will NOT have to write ANY code to perform property level validations on any DevForce generated entity type. If you are working with a custom property or an object type that is not a DevForce entity, you will need to write some of this scaffolding behavior yourself. For example: C# public String ShortName { get { return _shortName; } set { VerifierResultCollection verifierResults = _verifierEngine.ExecuteBeforeSet(this, "ShortName", value); if (verifierResults.Ok) { _shortName = value; } else { // Handle verifierResults here } } } } VB Public Property ShortName() As String Get Return _shortName End Get Set(ByVal value As String) Dim verifierResults As VerifierResultCollection = __ verifierEngine.ExecuteBeforeSet(Me, "ShortName", value) If verifierResults.Ok Then _shortName = value Else ' Handle verifierResults here End If End Set End Property Note that the ExecuteBeforeSet method above takes an object instance, a property name and a proposed value; the result is a VerifierResultCollection that contains a collection of individual VerifierResults as well as an aggregate Ok property. We have also assumed in the example above the availability of a VerifierEngine instance; _verifierEngine. DevForce actually does something a good deal more complex under the covers, because it calls both the ExecuteBeforeSet for 'beforeSet' verifiers and ExecuteAfterSet for 'afterSet' verifiers and interprets and handles verifier results in accordance with the VerifierOptions property of each verifier. Relevant VerifierOptions properties include ErrorNotificationMode, ErrorContinuationMode, TreatWarningsAsErrors and ShouldExitOnBeforeSetError. Instance level validation
Instance validation is much like property level validation except that you call the VerifierEngine's Executemethod instead of the ExecuteBeforeSet or ExecuteAfterSet methods. As with those methods, Execute also returns a VerifierResultCollection, whose individual members are each a VerifierResult. C# VerifierResultCollection aVerifierResultCollection = _verifierEngine.Execute(anEmployee); VB Dim aVerifierResultCollection As VerifierResultCollection = _verifierEngine.Execute(anEmployee){{/code}} Instance validation is called automatically by DevForce on the EntityServer just prior to the execution of a save, but most developers will want to call it programmatically in client side code as well. It is also possible to execute on demand a specified collection of verifiers, or a single verifier: C# Verifier birthDateVerifer = aVerifierEngine.GetVerifier( typeof(Employee), typeof(DateTimeRangeVerifier), Employee.EntityPropertyNames.BirthDate); aVerifierResultCollection = aVerifierEngine.Execute(anEmployee, new List<Verifier>() { birthDateVerifer }, null); VB Dim birthDateVerifer As Verifier = aVerifierEngine.GetVerifier(GetType(Employee), GetType _ (DateTimeRangeVerifier), Employee.EntityPropertyNames.BirthDate) aVerifierResultCollection = aVerifierEngine.Execute(anEmployee, New List _ (Of Verifier)() From {birthDateVerifer}, Nothing) However, this is unlikely to be a common operation in your application. Examine the validation results
You can examine a collection of VerifiersResults. Each VerifierResult consists of the following readonly properties: Property Property Type Description ResultCode VerifierResultCode A classification of the result of this validation, primarily a classification of the type of the success or failure of the operation. Message String A description of the result. Verifier Verifier The verifier that this VerifierResult resulted from. This will be null (Nothing in VB) if this is a "remote" VerifierResult. TargetInstance Object The object instance that was validated to get this result. VerifierOptions VerifierOptions The VerifierOptions on the Verifier that was executed to get this result VerifierContext VerifierContext The VerifierContext under which this validation was run. This will be null (Nothing in VB) if this is a "remote" VerifierResult. TriggerContext TriggerContext The TriggerContext under which this validation was run. The TriggerContext contains information about the timing (BeforeSet, AfterSet) of the validation among other things. PropertyNames ICollection<String> List of names of properties involved in this validation. The VerifierResultCodeenumeration consist of the following values: Value Description Ok Was this a successful validation. Error Was this a failed validation. OkWarning Was this is a "warning" result. Whether this is treated as a success or failure depends on the VerifierOptions.ShouldTreatWarningsAsErrors flag. OkNotApplicable Whether this result occured because the validation was not applicable to the data; ErrorInsufficientData Whether this result occured because of insufficient data. The following methods on the VerifierResult class provide a shorthand mechanism for accessing the actual enumerated values shown above: Method Success or Failure Description IsOk Success Was this a successful validation. IsError Failure Was this a failed validation. IsWarning Either Was this is a "warning" result. Whether this is treated as a success or failure depends on the VerifierOptions.ShouldTreatWarningsAsErrors flag. IsNotApplicable Success Whether this result occured because the validation was not applicable to the data; IsInsufficientData Failure Whether this result occured because of insufficient data. So for example the following two expressions mean the same thing: C# ValidationResultCode vrc = ... var isError = (vrc == ValidationResultCode.Error); // is same as var isError = vrc.IsError(); VB Dim vrc As ValidationResultCode =...var isError = (vrc Is ValidationResultCode.Error) ' is same as Dim isError = vrc.IsError() Note that both the Verifier and the VerifierContext properties will be null for any VerifierResult that occurs as a part of a "remote" validation. A "remote" validation is one where the validation occurs on the EntityServer instead of on the client. This is because, in such validations, the Verifier and its VerifierContext only exist on the EntityServer and we may not want these verifiers to be accessible to the client. In this case, we only want the results of the validation. Example The following code, for example, iterates through a collection of VerifierResults, captures the Message property value of each one that represents an error. (Note that a Verifier always returns a VerifierResult when executed, regardless of whether an error was found.) C# bool foundErrors = false; foreach (VerifierResult aVerifierResult in aVerifierResultCollection) { if (aVerifierResult.IsError) { foundErrors = true; _localOutput.Append(string.Format("\tValidation Failure: {0}\n", aVerifierResult.Message)); } } VB Dim foundErrors As Boolean = False For Each aVerifierResult As VerifierResult In aVerifierResultCollection If aVerifierResult.IsError Then foundErrors = True _localOutput.Append(String.Format(vbTab & _ "Validation Failure: {0}" & vbLf, aVerifierResult.Message)) End If Next aVerifierResult
Intercept a validation The ability to intercept a validation chain while it is executing and possibly change validation behaviors and/or results is a suprisingly common requirement. To accomplish this DevForce provides a pluggable delegate that, if defined, will be called after the execution of each verifier within the validation chain. This is called a VerifierBatchInterceptor .
A BatchInterceptor property is defined on the VerifierBatchInterceptor. C# public virtual VerifierBatchInterceptor BatchInterceptor { get; set; } VB Public Overridable Property BatchInterceptor() As VerifierBatchInterceptor The VerifierBatchInterceptor delegate is defined as: C# public delegate VerifierErrorContinuationMode VerifierBatchInterceptor( object instance, TriggerContext triggerContext, VerifierContext verifierContext ) VB Public Delegate Function VerifierBatchInterceptor( _ ByVal instance As Object, ByVal triggerContext As TriggerContext, _ ByVal verifierContext As VerifierContext) As _ VerifierErrorContinuationMode _ Implements VerifierEngine.VerifierBatchInterceptor with the following VerifierErrorContinuationMode enum values: Value Description Stop Stop executing, any verifiers further down the chain will be skipped. Continue Continue executing Inherit (See VerifierOptions inheritance discussion) Interception via the BatchInterceptor can be used to decide whether to continue with the current verification batch or to stop and return to the point in the program where the validation was triggered. The interception delegate also allows the currently executing validation's VerifierResultCollection to be modified. In other words, additional VerifierResults can be added or removed or summarized into a smaller number of VerifierResults after each verifier within the batch executes. One example of its possible uses is to stop the verification process when the verification results already contains more than a maximum number of errors. The following code sample below shows how you would create and use an interceptor. C# public static VerifierErrorContinuationMode EmployeeBatchInterceptor(Object instance, TriggerContext triggerContext, VerifierContext verifierContext) { if (verifierContext.VerifierResults.Errors.Count > 10) { verifierContext.VerifierResults.Add( new VerifierResult(false, "Too many errors, stop immediately")); return VerifierErrorContinuationMode.Stop; } else { return VerifierErrorContinuationMode.Continue; } }
public void DoEmployeeBatchInterceptor() { var mgr = new NorthwindIBEntityManager(); mgr.VerifierEngine.BatchInterceptor = EmployeeBatchInterceptor; mgr.VerifierEngine.DefaultVerifierOptions.ExecutionModes = VerifierExecutionModes.InstanceAndOnAfterSetTriggers; mgr.VerifierEngine.DefaultVerifierOptions.ErrorNotificationMode = VerifierErrorNotificationMode.Notify; var employee = mgr.Employees.FirstOrNullEntity(); employee.BirthDate = DateTime.Today.AddYears(7); //Verification batch will be executed here var results = mgr.VerifierEngine.Execute(employee); _localOutput.Append(results.Errors.Last().Message); } VB Public Shared Function EmployeeBatchInterceptor( _ ByVal instance As Object, ByVal triggerContext_Renamed As _ TriggerContext, ByVal verifierContext_Renamed As VerifierContext) _ As VerifierErrorContinuationMode If verifierContext_Renamed.VerifierResults.Errors.Count > 10 Then verifierContext_Renamed.VerifierResults.Add( _ New VerifierResult(False, "Too many errors, stop immediately")) Return VerifierErrorContinuationMode.Stop Else Return VerifierErrorContinuationMode.Continue End If End Function
Public Sub DoEmployeeBatchInterceptor() Dim mgr = New NorthwindIBEntityManager() mgr.VerifierEngine.BatchInterceptor = _ AddressOf EmployeeBatchInterceptor mgr.VerifierEngine.DefaultVerifierOptions.ExecutionModes = _ VerifierExecutionModes.InstanceAndOnAfterSetTriggers mgr.VerifierEngine.DefaultVerifierOptions.ErrorNotificationMode _ = VerifierErrorNotificationMode.Notify Dim employee = mgr.Employees.FirstOrNullEntity() employee.BirthDate = Date.Today.AddYears(7) 'Verification batch will be executed here Dim results = mgr.VerifierEngine.Execute(employee) _localOutput.Append(results.Errors.Last().Message) End Sub VerifierContext.EndOfBatch In addition to being fired after each verifier, the BatchInterceptor is also fired at the very end of the current verification batch. This is to provide useful behavior such as analyzing the results of the verification batch. Below is an example of the EmployeeBatchInterceptor delegate above modified by adding a simple logic using the VerifierContext.EndOfBatch property. The logic adds a new VerifierResult to the VerifierResultsCollection with a VerifierResultCode.Error and a string message to indicate how many total errors are accumulated at the end of batch. C# public static VerifierErrorContinuationMode EmployeeBatchInterceptor( Object instance, TriggerContext triggerContext, VerifierContext verifierContext) {
if (verifierContext.EndOfBatch) { verifierContext.VerifierResults.Add( new VerifierResult(VerifierResultCode.Error, String.Format("Accumulated a total of {0} error(s)", verifierContext.VerifierResults.Errors.Count))); return VerifierErrorContinuationMode.Stop; } else { if (verifierContext.VerifierResults.Errors.Count > 3) { verifierContext.VerifierResults.Add(new VerifierResult( VerifierResultCode.Error, "Too many errors, stop verification immediately")); return VerifierErrorContinuationMode.Stop; } else { return VerifierErrorContinuationMode.Continue; } } } VB Public Shared Function EmployeeBatchInterceptor(ByVal instance _ As Object, ByVal triggerContext_Renamed As TriggerContext, _ ByVal verifierContext_Renamed As VerifierContext) As _ VerifierErrorContinuationMode
If verifierContext_Renamed.EndOfBatch Then verifierContext_Renamed.VerifierResults.Add( _ New VerifierResult(VerifierResultCode.Error, _ String.Format("Accumulated a total of {0} error(s)", _ verifierContext_Renamed.VerifierResults.Errors.Count))) Return VerifierErrorContinuationMode.Stop Else If verifierContext_Renamed.VerifierResults.Errors.Count > 3 Then verifierContext_Renamed.VerifierResults.Add( _ New VerifierResult(VerifierResultCode.Error, _ "Too many errors, stop verification immediately")) Return VerifierErrorContinuationMode.Stop Else Return VerifierErrorContinuationMode.Continue End If End If End Function
Display validation results Now that the application is detecting invalid data and throwing exceptions, we had better think about how we want to handle those exceptions and tell the user what is going on. Verification with a Silverlight UI System.ComponentModel.INotifyDataErrorInfo is a Silverlight-only interface that defines members that data entity classes can implement to provide custom, asynchonous validation support. Important Silverlight user interface controls including the DataForm and DataGrid check for the implementation of this interface by data entities to which they are bound and provide excellent messaging in the user interface to help end users learn about and correct validation errors. Such Silverlight user interface controls do not depend upon exceptions being thrown in order to learn about validation problems. Instead, they subscribe to an ErrorChanged event published by the entity as part of its INotifyDataErrorInfo compliance. That event is raised by the data entity whenever it believes that a change to its state might be important to some control in the user interface. The UI control, learning of such a change, can then query the data entity via its INotifyDataErrorInfo.GetErrors() method to obtain a current set of validation errors, which it may then expose to the end user as its developer sees fit. Because of the support for INotifyDataErrorInfo by Silverlight UI controls, most developers working in Silverlight will want to set the system default for VerifierOptions.ErrorNotificationMode to Notify rather than any setting that results in exceptions being thrown for validation errors. Verification with a WPF UI Currently there is no WPF equivalent of INotifyDataErrorInfo; and WPF user interface controls typically rely upon exceptions as their mechanism for learning about validation errors and stimulus to display information about them. Hence to accommodate WPF UI controls most developers will want to set the system default for VerifierOptions.ErrorNotificationMode to ThrowExceptions or NotifyAndThrowExceptions . IdeaBlade.Core.INotifyDataErrorInfo A IdeaBlade.Core.ComponentModel.INotifyDataErrorInfo interface has been implemented in DevForce 2010 with the same semantics as the similarly named class in the Silverlight CLRs version of System.ComponentModel. This interface is implemented by the DevForce EntityWrapper, from which all of your DevForce-generated Entity types derive. Therefore all DevForce-generated entities, Silverlight or otherwise, publish the ErrorsChanged event, maintain a VerifierResultCollection, and provide a GetErrors method that returns the collection of those. Because of the implementation of this interface by Entity, it is now possible for you to configure your entities to collect validation errors rather than throw exceptions. You may also do both, if you wish. Again, for most purposes, VerifierOptions.ErrorNotificationMode should be set in Silverlight applications to notify but not throw exceptions, and in WPF applications to throw exceptions (and, optionally, notify if the developer wishes to use that capability for other reasons than interaction with the commonly available UI controls). A developer could choose, in a WPF application, to suppress exceptions and use the INotifyDataErrorInfo notification facilities instead. To do that, however, she would have to encode her UI explicitly so that it responds to the interfaces ErrorsChanged event, as the commonly available WPF controls know nothing about any INotifyDataErrorInfo interface or behavior. The INotifyDataErrorInfo workflow Here is the validation workflow that properly occurs when the facilities of INotifyDataErrorInfo are in use (as is standard in Silverlight 4 applications): 1. The end user changes the value of a property. 2. Verifiers triggered by the changes to that property are fired. 3. Each verifier that is run causes the INotifyDataErrorInfo.ErrorsChanged event to be raised. The event subscriber receives an ComponentModel.DataErrorsChangedEventArgs object that includes the name of the property whose error state may have changed. If there is no specific property whose error state may have changed, but yet the error state of the entity taken as a whole may have changed, then the subscriber receives a null value in ComponentModel.DataErrorsChangedEventArgs.PropertyName. 4. The control, which subscribes to the entitys INotifyDataErrorInfo.ErrorsChanged event (standard in Silverlight only!), receives notification that a change has occurred which may have affected the bound entitys error state. Said notification includes the name of the specific property whose error state has potentially changed, if there is a specific property. The control then calls INotifyDataErrorInfo.GetErrors() on the data entity, passing it the name of the property (if there was one) to get current information about the error state (of that property). GetErrors() returns a collection of VerifierResults. The UI then updates the displayed information about the error state of the property and/or the entity. A developer wishing to make a WPF UI respond the same way would have to code all of the behaviors described in step 4 above. Verification with a WinForm UI Data binding in WinForms is much more primitive than in either WPF or Silverlight. As a result, some of the processes described below are more complicated in this environment. UI lockup The UI is going to lock up the moment the user enters an invalid value into a verified UI control. That is any data entry control: TextBox), DataPicker), ComboBox, etc. The user will not be able to leave that control until she enters a value that passes validation not even to close the form. In this illustration, the user cleared the Last Name. The last name is required. The form displays an error bullet and prevents the user from moving out of the textbox.
How does the user recover? If this were a grid, she could press the [Esc] key; it is standard for grid controls to restore the previous value when the user presses escape. How many users know that? In any case, this TextBox is not in a grid and pressing [Esc] does nothing but ring an annoying bell. The user can press the standard key chord for undo: Ctrl+Z. How many users know that? No, the most users will just keep entering new values until they find one that lets them out of the field. Needless to say, a UI should apply the lock up enforcement technique sparingly. In the authors opinion, it makes sense only for a value the user must know and is sure to know a value that must be correct immediately and at all times. Dosage of a dangerous prescription drug would fit this bill. Few other properties qualify. Unlock the UI with AutoValidate Recall that the DevForce Entity.BeforeSetValue and Entity.AfterSetValue methods raise a VerifierResultException when the property fails validation. This exception bubbles up and out of the property setter. Data binding traps the exception. During the data binding Validate event raised when the user attempts to leave the TextBox. and responds by locking up the form. Fortunately, WinForms .NET 2.0 makes it easy to change this response. The key is the System.Windows.Forms.UserControl.AutoValidate property which takes one of the System.Windows.Forms.AutoValidate enumerations.
AutoValidate Description Inherit Do what the parent UserControl does. The parent is the UserControl that contains this UserControl. This is the default for new UserControl instances. If there is no parent, the value is the default, EnablePreventFocusChange. EnablePreventFocusChange Prevents the user from leaving the control until the value passes validation. EnableAllowFocusChange Validate but permit the user to leave the control if validation fails. Disable Does not validate. Generally not a good choice. Inherit is the default value for all new UserControls 1 . Inherit means that the UserControl is governed by the AutoValidate setting of its parent UserControls, the UserControl that contains it. The outer UserControl, typically a Form, doesnt 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 Forms 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 Forms setting. So the quick answer to UI lockup: Change the Forms AutoValidate property to EnableAllowFocusChange C# this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange; VB me.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange Improving the users experience EnableAllowFocusChange and BeforeSet triggers AutoValidate.EnableAllowFocusChange works great for property verifiers governed by BeforeSet triggers. The user can move out of the TextBox. Yet she can still see the error bullet protesting the lack of a last name.
The TextBox remains cleared so we can see that there is a problem or rather that there was a problem, that our intent to clear the name was invalid. The LastName property itself was never actually changed. A BeforeSet trigger prevents the property setter from updating the object. At the moment there is a discrepancy between the business object property value and the corresponding widget control display property on screen 2 . We can see reveal the discrepancy and cure it by scrolling off of the Nancy employee and then returning to her. The TextBox refreshes with her current LastName property value which remains Davolio. EnableAllowFocusChange and AfterSet triggers The behavior is different for verifiers evaluated in response to AfterSet triggers. If we had a LastNameRequiredVerifier and set its ExecutionModes to InstanceAndOnAfterSetTriggers, the LastName property value would be empty, just as it appears in the TextBox. A AfterSet trigger causes validation after the property has been set with the proposed value. We can confirm this by scrolling off of the Nancy employee and then returning to her. The TextBox remains blank. The current LastName property value is empty. However, we are no longer aware of the latent validation error. Our application does not validate the Employee upon display and that might be a user experience problem 3 . At least it is not a data integrity problem or doesnt have to be. We must assume that the application follows our advice and ensures that every entity must survive instance verification before it can be saved. We further assume that the application has some mechanism to display errant entities and their problems. Perhaps a simple MessageBox will do. This Employee will not survive validation, will not be saved, and the user will be told why. Questionable user experience This approach may be viable if little time can pass between data entry and instance verification. Some applications attempt a save whenever the user moves off the current screen. The user will never lose sight of the LastName error bullet and the save effort will reveal all latent problems with this employee. Many applications delay save and allow the user to move around among entities with pending changes. Thats how our tutorial works. Users can make a change to Nancy, scroll to Andrew and make changes to him, then scroll back to Nancy to continue her updates. In this kind of workflow, the user may not remember that there is a problem with the Nancy object for minutes or hours. When the application finally tells the user about this problem, the mental context is long gone and the application will be perceived to be unfriendly. There is another, potentially greater risk. The user may make a critical business decision base upon what is visible on the screen. That data could be in error. The user wont know it if she scrolled off and then back on to the record. If this risk is serious, the application must behave differently whenever the UI displays a new object a new Employee in our example. Instance verification upon display One approach would be to perform instance verification whenever the currently displayed object is changed. 1. ^ 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. 2. ^ 3. ^ 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 its not utterly trivial either. Well describe an approach that achieves something of that effect using a different technique. Validate on the server By default, any changes submitted via an EntityManager.SaveChanges call will be validated on the server as part of the save life cycle.. DevForce will automatically run instance verification on each entity in the save list. If there are any validation errors an EntityManagerSaveException is returned to the client. The FailureType on the exception will be set to PersistenceFailure.Validation , and the error message will be a concatenation of all validation error messages. The EntitiesWithErrors collection on the exception will also contain all of the entities which failed validation. The EntityAspect.ValidationErrors property on each of these entities will also return the validation errors. If you are binding to these entities in your UI the validation error(s) are displayed as they would with local validation failures (either on the property or instance). Be sure to wrap a synchronous SaveChanges call in a try/catch, and if calling SaveChangesAsync then be sure to call MarkErrorAsHandled to clear the error. C# var op = _mgr.SaveChangesAsync(); op.Completed += (o, args) => { if (args.HasError) { // If validation failed on server, mark as handled and let UI show errors. if (args.Exception.FailureType == PersistenceFailure.Validation) { args.MarkErrorAsHandled(); } // Check other save errors too. } } VB Dim op = _mgr.SaveChangesAsync() AddHandler op.Completed, Sub(o, args) If args.HasError Then ' If validation failed on server, ' mark as handled and let UI show errors. If args.Exception.FailureType = PersistenceFailure.Validation Then args.MarkErrorAsHandled() End If ' Check other save errors too. End If End Sub Server validation is performed by the EntityServerSaveInterceptor, which you can sub-type to customize save processing. The default VerifierEngine used is a single instance of a thread safe "concurrent" VerifierEngine. Verifiers are discovered and added to the server-side engine in the same way as they are in the client application, but it is not required that the same verifiers be defined on both client and server. In this way you can define verifiers which will only be executed on the client, or only executed on the server. To modify server-side validation, sub-class the EntityServerSaveInterceptor and override the ValidateSave virtual method. Localize error messages Last modified on March 22, 2011 16:28 Every verifier has a VerifierErrorMessageInfo property which in turn has a ResourceName property. This name is automatically generated (and set) during code generation for attribute based verifiers. For example, note the autogenerated ErrorMessageResourceNames on these attributes: C# [StringLengthVerifier(MaxValue=15, IsRequired=false, ErrorMessageResourceName="Employee_Country")] public string Country {} [RequiredValueVerifier(ErrorMessageResourceName="OrderDetail_Quantity")] public short Quantity {} VB <StringLengthVerifier(MaxValue:=15, IsRequired:=False, ErrorMessageResourceName:="Employee_Country")> _ Public ReadOnly Property Country() As String End Property <RequiredValueVerifier(ErrorMessageResourceName:="OrderDetail_Quantity")> _ Public ReadOnly Property Quantity() As Short End Property This resource name can also be set programmatically for any verifier defined in code. The VerifierEngine has an ErrorsResourceManager property that can assigned to like this: C# verifierEngine.ErrorsResourceManager = MyErrorMessages.ResourceManager; VB verifierEngine.ErrorsResourceManager = MyErrorMessages.ResourceManager where MyErrorMessages is the name of the strongly typed Designer.cs class generated by the .NET ResX editor. The VerifierEngine will look up each verifier's ResourceName in the registered ErrorsResourceManager in order to generate validation error messages. Regarding localization of these resources, please see Microsofts documentation for more information on this topic. The VerifierEngine has a fallback ResourceManager it uses for system default error messages. These resource names may be overridden in the programmatically specified ResourceManager. The fallback manager ResX is shown below. Most error messages contain substitution parameters; the meaning and order of these substitution parameters is defined by the type of verifier involved. For example: {0} is required. {0} must be between {1} and {2} character(s). By simply including {0}, {1}, and {2} in any message, you can easily determine what parameters are used for that verifier. Fallback ResourceManager ResX: XML <?xml version="1.0" encoding="utf-8"?> <root> <!-- Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format that is mostly human readable. The generation and parsing of the various data types are done through the TypeConverter classes associated with the data types.
Example:
... ado.net/XML headers & schema ... <resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="version">2.0</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> <data name="Bitmap1" mimetype="application/x- microsoft.net.object.binary.base64"> <value>[base64 mime encoded serialized .NET Framework object]</value> </data> <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <comment>This is a comment</comment> </data>
There are any number of "resheader" rows that contain simple name/value pairs.
Each data row contains a name, and value. The row also contains a type or mimetype. Type corresponds to a .NET class that support text/value conversion through the TypeConverter architecture. Classes that don't support this are serialized and stored with the mimetype set.
The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.bytearray.base64 value : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:element name="root" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="metadata"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" /> </xsd:sequence> <xsd:attribute name="name" use="required" type="xsd:string" /> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="mimetype" type="xsd:string" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="assembly"> <xsd:complexType> <xsd:attribute name="alias" type="xsd:string" /> <xsd:attribute name="name" type="xsd:string" /> </xsd:complexType> </xsd:element> <xsd:element name="data"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="resheader"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" /> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> </xsd:schema> <resheader name="resmimetype"> <value>text/microsoft-resx</value> </resheader> <resheader name="version"> <value>2.0</value> </resheader> <resheader name="reader"> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="VerifierAnd" xml:space="preserve"> <value>{0} and {1}</value> </data> <data name="VerifierBetween" xml:space="preserve"> <value>{0} must be between {1} and {2}</value> </data> <data name="VerifierBetweenDate" xml:space="preserve"> <value>{0} must be between {1:d} and {2:d}</value> </data> <data name="VerifierBetweenStringLength" xml:space="preserve"> <value>{0} must be between {1} and {2} character(s)</value> </data> <data name="VerifierInList" xml:space="preserve"> <value>Value must be among the valid choices for {0}</value> </data> <data name="VerifierMaxDate" xml:space="preserve"> <value>{0} must be on or before {1:d}</value> </data> <data name="VerifierMaxDateExclusive" xml:space="preserve"> <value>{0} must be before {1:d}</value> </data> <data name="VerifierMaxStringLength" xml:space="preserve"> <value>{0} cannot be longer than {1} characters(s)</value> </data> <data name="VerifierMaxValue" xml:space="preserve"> <value>{0} must be less than or equal to {1}</value> </data> <data name="VerifierMaxValueExclusive" xml:space="preserve"> <value>{0} must be less than {1}</value> </data> <data name="VerifierMinDate" xml:space="preserve"> <value>{0} must be on or after {1:d}</value> </data> <data name="VerifierMinDateExclusive" xml:space="preserve"> <value>{0} must be after {1:d}</value> </data> <data name="VerifierMinStringLength" xml:space="preserve"> <value>{0} cannot be shorter than {1} character(s)</value> </data> <data name="VerifierMinValue" xml:space="preserve"> <value>{0} must be greater than or equal to {1}</value> </data> <data name="VerifierMinValueExclusive" xml:space="preserve"> <value>{0} must be greater than {1}</value> </data> <data name="VerifierNotRequired" xml:space="preserve"> <value>{0} is not required</value> </data> <data name="VerifierRequired" xml:space="preserve"> <value>{0} is required</value> </data> <data name="VerifierValid" xml:space="preserve"> <value>{0} must be a valid {1}</value> </data> <data name="VerifierValidPattern" xml:space="preserve"> <value>{0} must be a valid {1} pattern</value> </data> </root>
Save Last modified on March 23, 2011 23:36 If an application decides to save, it issues one of the overloads of EntityManager.SaveChanges that can save an individual business object, an arbitrary list of objects, or all entities with pending changes. Saves are always transactional in DevForce. Unmodified entities are never saved. Attempts to save them are ignored. If concurrency checking is enabled, DevForce will confirm that entities being saved have not been modified or deleted by another process since they were last retrieved. 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 EntityState enumerations. Initiation of any save operation causes the EntityManager to attempt to replace temporary ids with permanent ids. Add, change, and delete operations only affect entities in an EntityManager's entity cache. They are not written to the data source nor are they visible to other application users until the application tells the EntityManager to save them. Alternatively, the application can undo the changes rather than save them. Save-related topics elaborate on these points Perform a save
Saving is the process of sending a collection of added, modified or deleted objects to some backend datastore and having them be persisted there. The basics of performing a save are described on this page. A description of the ways in which a save operation can be intercepted or mediated will be discussed in subsequent pages.
Save API In order to perform a save in DevForce, one of the following methods must be called. Synchronous methods: public SaveResult EntityManager.SaveChanges() public SaveResult EntityManager.SaveChanges(SaveOptions saveOptions) public SaveResult EntityManager.SaveChanges(IEnumerable entities, SaveOptions saveOptions = null) Asychronous methods: public EntitySaveOperation EntityManager.SaveChangesAsync() { public EntitySaveOperation EntityManager.SaveChangesAsync(Action<EntitySaveOperation> userCallback, object userState = null) public EntitySaveOperation EntityManager.SaveChangesAsync(SaveOptions saveOptions, Action<EntitySaveOperation> userCallback = null, object userState = null) public EntitySaveOperation EntityManager.SaveChangesAsyncIEnumerable entities, SaveOptions saveOptions = null, Action<EntitySaveOperation> userCallback = null, object userState = null) Determining which entities to save Two of the overloads shown above take a list of entities as the first argument. These versions allow a developer to explicitly specify which entities to save. The overloads which do not take a list of entities assume that all entities found in the EntityManager's cache that are marked as either added, modified or deleted should be saved. If either of the overloads specifying a list of entities is used, DevForce will perform an additional check prior to sending these entities to the server. This check is to insure that if any of the specified entities has a temporary id then every other entity that has a reference to this id must also be in the save list. If this is not the case, DevForce will throw an exception, with a description of which entities are causing a problem. Essentially, DevForce will not allow a 'partial' save that could cause data corruption. This is not an issue when DevForce determines which entities to save. Saving a single entity You'll notice that all SaveChanges methods will either accept an IEnumerable of entities or will save all changes in the entity cache. This may at first seem confusing if you need to save only a single entity. It's easy, though; just create a list, maybe a List<Entity>, and add the changed entity to it. Or create an array on the fly, like so: C# manager.SaveChanges(new[] { aChangedCustomer }); VB manager.SaveChanges(New () {aChangedCustomer}) The SaveOptions class Every save performed by DevForce makes use of an instance of the SaveOptions class. This instance is provided either by passing it into the SaveChanges call or by using value of the EntityManager's DefaultSaveOptions property. Note that several of the overloads above take a SaveOptions argument, those that do not make use of the EntityManager.DefaultSaveOptions. The EntityManager.DefaultSaveOptions property can be modified at any time to change these defaults. Most of the time the default values for SaveOptions are sufficient and there is no need to use one of the SaveChanges overloads that takes a SaveOptions parameter. The SaveOptions class has several interesting properties: Property PropertyType Access
Description TransactionSettings TransactionSetti ngs get set Gets or sets the transactional behavior for the save. This property is described in more detail here: save-transactions. Tag Object get set Used to pass custom user information to the entityserver regarding the save operation. This property can be very useful in controlling the behavior of an EntityServerSaveInterceptor. The value of the SaveOptions property, including this tag, is passed to each instance of the EntityServerSaveInterceptor during its construction. The value of this property must be serializable by WCF. The default value for this property is null (Nothing in VB). FixupTempIds FixupTempIds get set Gets or sets the option to fixup all generated temporary Ids or only those Ids corresponding to entities passed into the SaveChanges method. Possible values are FixupTempIds.All Default setting. Perform Id fixup on all generated temporary Ids. FixupTempIds.InSaveList Only Perform Id fixup only on those entities passed into the SaveChang es method. This property is only relevant when a SaveChanges call is made that takes a collection of entities. EntityTypesExcludedFromPostSaveR efetch IList<Type> get set Gets or sets the types of entities to be excluded from the refetch that is performed after a save. By default all entity types will be refetched after a save, this setting allows a developer to improve performance by telling DevForce which types cannot 'change' as a result of the save and where a refetch is therefore unnecessary. PocoSaveMode PocoSaveMode get Determines how to discover any set custom server side POCO save methods. and one useful method: Method Signature Description public SaveOptions WithTag(Object tag) Used to create a clone of this SaveOptions but with the "Tag" set to the specified value. The SaveResult class The synchronous SaveChanges calls return a SaveResult while the SaveChangesAsync versions all return an EntitySaveOperation which contains a SaveResult property. The SaveResult type contains the following properties: Property PropertyType Access
Description Ok bool get Returns whether or not the save succeeded. This will be true, unless the Save was either canceled or had an exception that was 'handled'. SaveStatus SaveStatus get Returns the kind of operation performed during this save. SaveStatus Description Incomplete The operation has not yet completed. NoOperation There was nothing to save, there were no added, modified or deleted entities to save ( and no many-many relationship changes). Cancelled The operation was cancelled either in a Saving handler or in the EntityServerSaveIntercept or. ExceptionHandl ed An exception was both thrown and then handled by an EntityServerErrorIntercep tor handler Normal The save completed normally.
SavedEntities IList<Object> get List of entities that were saved. WasCancelled bool get Returns true if the save was canceled, either during the Saving event or from within the EntityServerSaveInterceptor. WasExceptionHandl ed bool get Returns true if an exception was thrown but handled by an EntityServerError handler; otherwise false. HandledException EntityManagerSaveExcepti on get Returns the handled exception or null (Nothing in VB) if no exception or unhandled. TemporaryIdMap UniqueIdMap get Gets copy of the UniqueIdMap that was (or would have been) used for fixup. SaveResult.Ok returns "true" if the save was entirely successful. ( A SaveStatus of NoOperation is still considered 'Ok'). If the save was canceled in a life-cycle handler, SaveResult.WasCancelled will return true and SaveResult.Ok will return "false". If the save threw an exception and was handled, SaveResult.WasErrorHandled will return "true" and SaveResult.Ok will return "false". If a save throws an exception and is not handled, no synchronous result will be returned at all because an exception will have been thrown. If this occurs during an asynchronous call then SaveResult.WasErrorHandled will be false and SaveResult.Ok will return "false". After the Save DevForce immediately re-queries all inserted or updated entities after saving them successfully. This re-query is essential because the insert or update may provoke a data source trigger that modifies the data source object. We often use a trigger to update an optimistic concurrency column. A database-maintained timestamp is another common example. In such cases, the row in the database is no longer exactly the same as the row we wrote. The SaveOptions.EntityTypesExcludedFromPostSaveRefetch property ( mentioned above) may be used to modify this behavior. After the refetch the EntityServer sends the possibly 'updated' entity back to the clients EntityManager. The revised entity is merged back into the cache, replacing the original and its EntityState becomes Unchanged. Perform a save asynchronously DevForce provides asynchronous save functionality for two reasons: Firstly, the Silverlight and WP7 CLR (common language runtime) environments do not permit synchronous web service calls. Secondly, even in those environments where synchronous API's are supported, there are use cases where an asynchronous save can be useful. While an overview of asynchonous programming in DevForce is also available; the remainder of this topic discusses the specific asynchronous API's provided in the context of saving.
EntitySaveOperation and EntitySavedEventArgs The return value from any call to one of the EntityManager.SaveChangesAsync overloads is an instance of an EntitySaveOperation. This operation result can be either be accessed directly within the method's callback logic or via the operation's Completed event. In the case of the Completed event; the event delegate gets passed a EntitySavedEventArgs parameter. Regardless of whether you are working with an EntitySaveOperation or an EntitySavedEventArgs, the following readonly properties are provided in addition to those inherited from the BaseOperation or AsyncEventArgs base classes. Please see Asynchronous Programming for more detail on the 'standard' DevForce asynchronous model and the properties available there. The following discussion is only about those additional properties that are available during an asynchonous save. Property Property Type Description Entities IList<Object> Returns the entities saved. SaveResult SaveResult Returns the SaveResult - see SaveResult Exception EntityManagerSaveException Returns the exception thrown if an error occurred during save processing. Example C# // Using lambda syntax: myEntityManager.SaveChangesAsync(op => { var savedEntities = op.Entities; var sr = op.SaveResult; var error = op.Exception; });
// Using Completed event syntax: var op = myEntityManager.SaveChangesAsync() op.Completed += (sender, eventArgs) => { var savedEntities = eventArgs.Entities; var sr = eventArgs.SaveResult; var error = eventArgs.Exception; }; VB ' Using lambda syntax: myEntityManager.SaveChangesAsync(Sub(op) Dim savedEntities = op.Entities Dim sr = op.SaveResult Dim err = op.Exception End Sub)
' Using Completed event syntax: AddHandler Dim op = myEntityManager.SaveChangesAsync() _ op.Completed, Sub(sender, eventArgs) Dim savedEntities = eventArgs.Entities Dim sr = eventArgs.SaveResult Dim err = eventArgs.Exception End Sub
Validate before a save By default DevForce validates every entity on the server before saving it. This behavior can be modified by overriding the ValidateSave method in a custom EntityServerSaveInterceptor. Client side validation is not performed automatically but can be easily accomplished via the EntityManager.Saving event.
Handling validation errors If any validation errors are encountered on the server, there are two mechanisms by which these errors are reported back to the EntityManager that performed the save. Any errors will be propagated back to the "errant" entities within the calling EntityManager's cache and each of these entities will have its EntityAspect.ValidationErrors property automatically updated. Providing that these entities have been 'bound' to a client side UI and because of the eventing behavior provided by the ValidationErrors property and DevForce's implementation of the INotifyDataErrorInfo and IDataErrorInfo interfaces; these errors should appear immediately in the client side UI. The EntityManagerSaveException.EntitiesWithErrors property of the EntityManagerSaveException reported by the EntityManager.Saved event and the EntitySaveOperation.Completed event, if the save call was asynchronous, will both contain these 'errant entities'. Any errors encountered can then be 'fixed' and the same save resubmitted again. Handle save errors The SaveResult returned by both synchronous and asynchronous SaveChanges calls provides information which can help you handle save errors.
SaveResult.Ok returns "true" if the save was successful. If the save was canceled in a life- cycle handler, SaveResult.WasCancelled will return "true" and SaveResult.Ok will return "false". If a save fails for any reason except 'Cancelation' an EntityManagerSaveException is raised. For synchronous SaveChanges calls the exception is thrown, while in SaveChangesAsync calls the exception is available on the EntitySaveOperation. Handling the exception You should prepare your code to trap and analyze the save exception. The EntityManagerSaveException has the information you need to help diagnose and handle the problem. Always handle save exceptions. Wrap every call to EntityManager.SaveChanges() in your own custom Save method. Wrap every synchronous SaveChanges in a Try/Catch. Add error logic to every asynchronous SaveChangesAsync to analyze the exception. For asynchronous calls, remember to call MarkErrorAsHandled unless the application should terminate. Heres a code fragment showing a SaveAll method that matches our recommendation: C# internal void SaveAll() { try { MainEm.Manager.SaveChanges();// Save everything DisplaySaveOk(); } catch (EntityManagerSaveException saveException) { ProcessSaveFailure(saveException); } catch { throw; // re-throw unexpected exception } } VB Friend Sub SaveAll() Try MainEm.Manager.SaveChanges() ' Save everything DisplaySaveOk() Catch saveException As EntityManagerSaveException ProcessSaveFailure(saveException) Catch Throw ' re-throw unexpected exception End Try End Sub The serious failure interpretation and recovery work is in the ProcessSaveFailure method, which we leave to you. Your application should determine how to process save errors, and recover from then when possible. EntityManagerSaveException The EntityManagerSaveException is raised for all save-related exceptions. If you've added a handler to the EntityManager's EntityServerError event, that handler will be called first when a save-related exception occurs. If there is no handler or it doesnt handle the exception, the EntityManager throws it again, now in the context of the SaveChanges call. We recommend that you do not handle save exceptions in an EntityServerError handler; leave that to the code near your SaveChanges call that traps and interprets save failures. The EntityManagerSaveException inherits from EntityServerException, supplementing it with information about which entity/entities in the local cache caused the problem. There are several properties on the EntityManagerSaveException that can be very useful in diagnosing the problem that occurred. Property Property type Description EntitiesWithE rrors IList<Obj ect> A list of the entities with errors. In practice, this will usually be a list of one -- the first entity to fail --. The idea here is that a non-transactional save might have multiple errors because one error would not stop the process, or multiple entities may have failed validation. Since DevForce only offers transactional saves to the datastore, the first entity to fail within a transaction causes the transaction to rollback, so no other entities are saved. InnerExceptio n Exceptio n 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. FailureType FailureTy pe A classification of the error that cause the problem. Several FailureTypes are possible. FailureType Description Connection The Entity Manager could not reach the data source. There might be a network connection problem or the data source itself could be down. Data The data source threw an exception such as a referential integrity violation. Concurrency There was a concurrency conflict. Validation One or more entities failed server-side validation. Other Could be anything.
What happens to any local entities after a save fails These entities remain in the cache and retain exactly the values and setting they had before the save attempt. This means that you may be able to correct the entities and attempt to re-save them. Manage concurrency Anytime we save an entity in a multi-user distributed environment, there is the possibility that another user on another machine has saved their own version of the same entity since we last queried for our own copy of the entity. This page describes the mechanisms for managing concurrency.
The problem A multi-user application must decide how to resolve the conflict when two users try to update the same entity. Consider the following: 1. I fetch the Employee with Id = 42 2. You fetch the Employee with Id = 42 3. You change and save your copy of the Employee with Id = 42 4. I try to save my copy of the Employee with Id = 42 Is this really going to happen? There is always a risk that another client or component will change the data source entity while we are holding our cached copy of it. The risk grows the longer we wait between the time we fetch the entity and the time we save or refresh it. In offline scenarios, the time between fetch and update can be hours or days. There could be a great many concurrency conflicts waiting to happen. If I save my copy now, should it overwrite the one you saved? If so, weve chosen "last-in-wins" concurrency checking. My copy replaces your copy; your changes are lost. This is the default in DevForce but we strongly recommend that you adopt another type of concurrency control. Permitting one user to blithely overwrite changes that another user made can be dangerous or even fatal. There is an enormous body of literature on this subject available on the web by searching for "concurrency checking". Basic mechanics of concurrency detection DevForce defers to the ADO.NET Entity Frameworks mechanism for detecting concurrency conflicts at the time an update is submitted to the back-end data source. The Entity Framework (EF) permits the developer to designate, in the Entity Model, one or more entity properties as concurrency properties by setting its "Concurrency Mode" to "Fixed". Note that EF does not allow you to define a concurrency property within a Complex Type. When a client application submits an update order against such a model to the EF, the EF prepares a SQL Update statement. To that statement it adds a WHERE clause that ensures that all columns designated as a concurrency columns have the same value they did when the record was last retrieved by the submitting application. (In other words, they have not been changed in the meantime by another user.) If that proves not to be the case, the exception thrown by the back-end data source will be propagated back down the applications calling chain. This approach works only if the concurrency properties are updated with each successful save. Entity Framework handles concurrency violation detection but not concurrency property update. It is the developers responsibility to ensure that the update happens. You can perform the update yourself, tell DevForce to do it, or rely on the database to update the corresponding concurrency column itself (as it might do with an update trigger). You tell DevForce which approach to take by setting each concurrency property's "Concurrency Strategy" in the EDM Designer.
You can pick one of six concurrency property update strategies: Strategy Name Strategy for updating the concurrency property AutoGuid DevForce replaces the existing property value with a new GUID. AutoDateTime DevForce replaces the existing property value with the current Date/Time. AutoIncrement DevForce increments the existing property value by 1. Server Callback DevForce finds your custom class that implements the IConcurrencyStrategy interface and calls its SetNewConcurrencyValue() method, identifying the concurrency property and the entity-to-update in the parameters. Your implementation of SetNewConcurrencyValue() updates the property. Client Your custom code in the client application updates the property before sending modified entities to the server to be saved. None Neither you nor DevForce update the property. Appropriate when the data store automatically updates the concurrency column as it might do with a trigger. 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 developers responsibility to handle any concurrency exception thrown by the back end. Here's an example:
A. The RowVersion concurrency property of the Customer conceptual entity B. The "Concurrency Strategy" DevForce uses to update the concurrency property C. The "Concurrency Mode" (Fixed) tells Entity Framework that this is a concurrency property The "Concurency Strategy" in this example is None meaning that we expect the database to update the RowVersion automatically, probably with an update trigger. Alternatively, DevForce would update it if we picked AutoIncrement. Remember: 1. Set the Entity Framework "Concurrency Mode" to Fixed or nothing happens. 2. When "Concurrency Strategy" is Auto, DevForce updates the property for you; otherwise, you handle update whether on server, client, or data tier. One concurrency column, or many? Since the Entity Framework permits you to designate any number of columns as concurrency columns, it may be tempting simply to designate them all. Thats one way of making sure that, if anything in the record has been changed by another user since you got your copy, a concurrency conflict will be diagnosed. This may be your only alternative if you have no design access to the database, but be aware that there will be a performance impact. Every update will be accompanied by a flurry of activity comparing values. As with other performance issues, you should do some upfront testing to determine whether the performance impact is unacceptable, or even significant. If you do have design access to the database, or youre fortunate enough to inherit a database already designed the way you want it, its generally a better alternative to provide a single column that is guaranteed to be updated whenever anything else is, and to use that as your sole determinant of a concurrency conflict. A simple integer column that is incremented each time the record is updated will do quite nicely; you can also use a GUID, 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. Pessimistic versus optimistic concurrency checking There are two popular approaches to concurrency checking: pessimistic and optimistic. In pessimistic concurrency, we ask the data source to lock the data source entity while we examine it. If we change it, we write it back to the data source. When we are done looking at it or updating it, we free the lock. While we have it locked, no one else can see or use it. This approach holds up other users trying to reach the object we hold. It gets worse if we need many objects at once. There are potential deadlocks (I grab A, you grab B, I want B too, but cant get it, so I wait. You want A also, but cant get it, so you wait. We both wait forever). There are more complicated, less draconian implementations to this approach but they amount to the same punishing performance. Under optimistic concurrency, we dont lock the table row. We bet that no one will change the source data while were working with it and confirm our bet when (and if) we try to update the data. The mechanism works as follows. We fetch a copy of the table row and turn it into a business object. We work with this copy of the data source entity. We may decide to update the entity or mark it for deletion. When we save an altered entity, the business object server converts our intention into a data source management command. That command, in the process of updating or deleting the supporting table row, confirms that the row still exists and has not changed since we fetched it. If the row is missing or has changed, the command fails and its up to the application to figure out what to do about it. Changes are comparatively rare so we have reason to be optimistic that the row will be exactly as we last found it. Only optimistic concurrency checking is performed in both DevForce and the Entity Framework. Resolving concurrency collisions Our optimism is usually rewarded. Occasional disappointment is inevitable. Eventually, we will encounter a conflict between our cached entity, with its pending changes, and the newly- updated data source entity. We will want to resolve that conflict one way or the other. The possible resolutions include: Preserve the pending changes and ask the user what to do. Abandon the pending changes and re-fetch the entity. Arrange for the cached entity to become the current entity while preserving the pending changes Compare the cached entity with the current data source entity and merge the difference per some business rules or as guided by the user. 1st choice - Stop and ask 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 MergeStrategy and how we use it. C# aManager.RefetchEntity(anEntity, aMergeStrategy); VB aManager.RefetchEntity(anEntity, aMergeStrategy) 2nd choice - Discard local changes and refresh from the datasource The second choice uses the OverwriteChanges strategy to simply discard the users 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. Note that in many scenarios concurrency collisions occur so infrequently that this can be a very reasonable choice. 3rd choice - Force the original thru despite concurrency violation 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 entitys original version, ( see EntityState discussion) with the values from the current data source entity but it preserves the cached entitys current version values, thus retaining its pending changes. The cached entitys original concurrency column value now matches the concurrency column value in the datasource record. C# // the current value of the property in the cached entity Employee.PropertyMetadata.FirstName.GetValue(anEmployee, EntityVersion.Current); // the value from the datasource when most recently retrieved Employee.PropertyMetadata.FirstName.GetValue(anEmployee, EntityVersion.Original); VB ' the current value of the property in the cached entity Employee.PropertyMetadata.FirstName.GetValue(anEmployee, _ EntityVersion.Current) ' the value from the datasource when most recently retrieved Employee.PropertyMetadata.FirstName.GetValue(anEmployee, _ EntityVersion.Original) The effect is as if we had just read the entity from the datasource and applied the users changes to it. If we ask the persistence layer to save it now, the datasource will "think" that we modified the most recently saved copy of the entity and welcome the changed record. This option is much like "last one wins" concurrency with a crucial difference: it was no accident. We detected the concurrency collision and forced the issue in accordance with approved business rules. PreserveChangesUpdateOriginal strategy works only if the entity is governed by optimistic concurrency. If the entity lacks a concurrency column, the refetch uses the OverwriteChanges strategy instead. Of course we wouldnt be talking about concurrency resolution if there were no concurrency columns. 4th choice - Merge local changes with refetched data The fourth possibility begins, like the third, with a re-fetch governed by the PreserveChangesUpdateOriginal strategy. This time we dont 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 entitys values should prevail. The logic may be entirely automatic. Alternative, the program could present both versions to the user and let her decide each difference. Concurrency and the object graph One interesting concurrency issue revolves around the scope of concurrency checking. Have I changed an Order if I add, change or delete one of its OrderDetail items? If I change the name of a customer, have I changed its orders? These considerations have to do with concurrency control of the business object graph. DevForce does not support graph concurrency directly. DevForce supports single-table, business object proper concurrency control. The developer can achieve the desired degree of graph concurrency control by employing single-table checking within a properly conceived, transactional concurrency plan. It doesnt have to be wildly difficult. In brief, the developer adds custom business model code such that Added, changed, or deleted children entities always modify their parents. An application save attempt always includes the root entity of the dependency graph. During a save attempt, the root entity ensures that its children are included in the entity-save list. These children include their children. Concurrency and mutually dependent entities What if a bunch of entities are mutually dependent? Suppose we have an order and its details. User A adds two more details and changes the quantity on a third. She deletes the fourth detail and then saves. In many applications, an order is never less than the sum of its parts. The order and every one of its details must be treated as a unit at least for transactional save purposes. We will describe this network of dependency as a "Dependency Graph". 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 didnt touch the fifth order detail. She wont know about the change because there will be no concurrency conflict to detect; she cant detect a concurrency conflict unless she save the fifth order detail and she has no reason to do so. If this worries you (it worries me), you may want to establish business rules that detect concurrency violations for any of entity in a dependency graph. A good approach is to Identify the root entity of the graph (Order) and Ensure that a change to any node in the graph (OrderDetail) causes the root entity to change. User Bs change to the fifth detail would have meant a change to the order. User As changes also modified the order. User As save attempt will provoke a concurrency violation on the root entity, the order. Resolution Now that User A has learned about the violation, what can she do? There is no obvious problem. Neither A nor B changed the order entity itself so there are not differences to reconcile. There is only the tell-tale fact that their concurrency column values are different. It doesnt 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 didnt change. It should look for differences 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 cant 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 weve 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 well put User As 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 As order and details into the second manager and put User Bs 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. Wed 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 Bs order; if so that order will be in the main manager where it belongs. Show some restraint The orders entire object graph is not its dependency graph. The order may consist of details but that may be as far as it goes. For example, every detail is associated with a product. If User B changed the fifth details product name or its color, should this provoke a concurrency conflict? It User C updated the orders customer name, does that mean all orders sold to that customer must be scrutinized for concurrency collisions. Most businesses will say "no". Manage the save transaction EntityManager save methods can save a single business object, a list of objects, or all entities with pending changes. All 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 condition. SaveOptions.TransactionSettings
The transactional nature of any save is controlled by the value of the TransactionSettings property on the SaveOptions class. Recall that every save has a SaveOptions instance applicable to it. This is either the EntityManager's DefaultSaveOptions or a standalone SaveOptions instance specified within the SaveChanges call. The TransactionSettings property is of type TransactionSettings and contains the following properties, all of which can be set. Property PropertyType Default value Description IsolationLevel IsolationLevel ReadCommitted Determines the transactions locking behavior. Please consult your database documentation for more info. Timeout Timespan TimeSpan(0, 1, 0) - 1 minute The timeout period for the transaction. UseTransactionScope bool true It is highly recommended that this be set to true (default). It is this setting that causes a transaction that crosses database boundaries to escalate to use the DTC (Distributed Transaction Coordinator). Most SQL databases define different degrees of consistency enforcement called "isolation levels". Each database vendor has a different default isolation level and a proprietary syntax to change it. Setting the IsolationLevel above is dependent upon the support of both the database vendor and the .NET ADO.NET provider being used. In some cases, settings may need be made within the database itself or with proprietary information embedded in the connection string. Consult the database vendors documentation. Distributed transactions By default, DevForce also provides 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 transactions. By setting the UseTransactionScope property mentioned above to "true" ( the default), the developer is instructing DevForce to use the .NET Enterprise Services (AKA, COM+) Distributed Transaction Coordinator (DTC) to handle transaction management when more than one database is involved. Be sure to enable DTC in your system. (Control Panel > Administrative Tools > Services) 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. Save lifecycle Last modified on March 21, 2011 19:13 Workflow for a save During the execution of a save a number of discrete steps are followed. DevForce provides several points along the way where a developer can intervene and modify the execution path. This page describes this sequence of steps and the interception points. Component Action Client Tier Application Code The client application adds, modifies and deletes any number of business objects on the client. The client application asks a EntityManager to save all pending changes. Client Tier EntityManager Makes a save list of all of the new, modified, and deleted entities in the EntityManager entity cache. Fires the Saving event, passing in the list of entities to be saved. The listener can add or remove entities from this list or cancel the save. For now, let's assume that application listener just okays the save. If this is a partial save, meaning that only specified entities are being saved instead of all added, modified, or deleted entities, then a check is performed to insure that if any entity in the graph of entities being saved contains a temporary id, that all other entities that have a reference to this same temporary id are also included in the save. If this is not the case an Exception is thrown with an explanation of which entities are in violation of this rule. Transmits the list of entities to be saved to the EntityServer. Middle Tier Server authenticates the user. Let's assume success. EntityServer The EntityServer creates a new EntityServerSaveInterceptor or an instance of a developer customized subclass. In the EntityServerSaveInterceptor the AuthorizeSave method can be used to perform security checks; the ValidateSave method can be used to perform server-side validation. When the EntityServerInterceptor.ExecuteSave method is called, where DevForce performs a temporary id to permanant id conversion and then forwards the saves to the Entity Framework for execution. Data Tier Data Source Performs the persistence operations. If there are no failures, it commits them; if there is a single failure, it rolls them all back. Middle Tier EntityServer If the transaction failed, returns to the EntityManager the identity of the culprit entity and the exception raised by the data source. The EntityManager stores this information in the SaveResult and returns to the client application. Workflow ends. Otherwise The transaction succeeded. The EntityServer 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 servers local copy of the entities go out of scope and the garbage collector reclaims them. This enables the object server to stay stateless. Client Tier EntityManager Performs id fixup, converting temporary ids to permanent ids based on a mapping between the two generated by the EntityServer during the save and passed back to the client in the previous step. Replaces cached entities with updates from EntityServer. They are marked unchanged because they are now current. Raises the Saved event with list of saved inserted and modified entities. Client Tier Application Code The application resumes. Client-side save lifecycle events The client side lifecycle of a save consists of two events on the EntityManager; Saving and Saved. These are summarized in the table below.
Saving and Saved events Event EventArgs Comments Typical Uses of the Corresponding Event Handler Saving EntitySavingEventArgs Raised when the EntityManager is preparing to save changes. Provides access to the collection of entities submitted for the save. Permits cancellation of the save. Modify the object submitted for saving, or refuse the request to perform inserts and/or updates. Saved EntitySavedEventArgs Raised when the EntityManager has completed a saved (including a cancelled save). Provides access to the collection of entities 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 Saving event shortly after the application initiates a save. It raises a Saved event upon completion of the save, regardless of whether any entities were 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 list. SaveChanges() with no arguments, for example, is a blanket request to save every added, changed or deleted entity in cache. 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 upon completion of the save. The handler receives a list of the entities that were saved successfully. In DevForce, saves are always transactional, even across disparate back-end data sources. In transactional saves, either every entity in the save list is saved or none of them are. Virtual EntityManager methods
Corresponding to each of these events is a protected virtual method on the EntityManager that can be used to perform the same task directly in a subclassed EntityManager. Member Summary OnSaving The virtual method that a derived EntityManager can override to raise the Saving event. OnSaved The virtual method that a derived EntityManager can override to raise the Saved event. EntitySavingEventArgs Property PropertyType Access Description Entities IList<Object> get - but the list returned can be modified The list of entities to be saved. Cancel bool get, set Allows the save to be cancelled. EntitySavedEventArgs Property PropertyType Access Description Entities IList<Object> get Returns the entities saved. SaveResult SaveResult get Returns the SaveResult - see SaveResult Exception EntityManagerSaveException get Returns the exception thrown if an error occurred during save processing. Server-side save lifecycle events The changes-set of entities to be saved are processed by the EntityServer which passes the change-set through an instance of the DevForce EntityServerSaveInterceptor. You can alter the course of that processing by creating a 'custom' subclass of the EntityServerSaveI nterceptor and overriding its virtual properties and template methods. DevForce can discover the existence of your class automatically.
Interceptor design guidelines You don't have to write a custom interceptor class. The EntityServer will use the DevForce EntityServerSaveInterceptor if it doesn't find a custom subclass. Many production applications do include a custom interceptor.
The EntityServerSaveInterceptor class resides in the IdeaBlade.EntityModel.Server assembly which must be referenced by the project that contains your custom subclass. You can write only one custom interceptor. It should be a public class and must have a public default, parameterless constructor (if it has a constructor). Please keep your named custom interceptor stateless if possible. DevForce creates a new instance of this class for each query performed by the server so you generally don't have to worry about threading issues with instance state. If you decide to maintain static state, give great care to ensuring safe concurrent access to that state. Avoid putting anything in the interceptor other than what is strictly necessary to achieve its purpose. The interceptor is a poor choice for a grab-bag of server-side features. You don't have to override any of the template methods; the default base implementations all work fine. You may wish to be explicit in your custom class and override every template method; your override can simply delegate to the base implementation. Make sure that the assembly containing your custom interceptor is deployed to the server such that it can be discovered. Assembly discovery is discussed here. Interceptor template methods The EntityServerSaveInterceptor class contains a number of template methods that you can override to modify the base behavior of the class. These methods are executed at key points within the server-side part of the save process; you override them in your subclass to perform custom interventions. They enable you to perform operation both before and after the physical save to the database (or equivalent backing store). Most of the template methods have no parameters because all of the relevant data are provided by properties and methods on each instance of the class. This also allows IdeaBlade to extend the base class in the future without breaking custom developer code. Many of the template methods described below return a boolean result with a base implementation that returns "true". A return value of "true" allows the save to continue. A return value of "false" causes the save to exit with an exception. There is also a flag available that may be used to indicate a "canceled" operation. Note that while the base implementation of the authorization method does not return false, it will throw an exception it it detects an unauthorized save. It treats an unauthorized save as an exception, not a cancellation. protected virtual bool AuthorizeSave() Override to control whether the user is authorized to perform the save. The base implementation walks all of the types involved in the save and calls the ClientCanSave method defined below for each to determine if any unauthorized types are being accessed. An EntityServerException will be thrown with a PersistenceFailure value of "Authorization" if any unauthorized types are encountered. You can bypass authorization by simply returning "true" without calling the base implementation. protected virtual bool ValidateSave() Override to extend ( or remove) server-side validation of the data to be saved. The base implementation of this method will perform "instance validation" for each entity being saved. If any verification fails, an EntityServerException will be thrown with a PersistenceFailure type of "Validation". The VerifierEngine property is available in order to discover what validations will be performed. You can bypass validation by simply returning "true" without calling the base implementation. protected virtual bool ExecuteSave() Override to intercept the save immediately before and after the save request is executed. The entities involved in the save may be modified before calling the base implementation of this method and logging or other post processing operations may be performed after the base implementation is called. Note that the base implementation must be called in order for the save to be executed. The EntityManager property defined below contains all of the entities to be saved and its contents may be modified at any time prior to the base implemention of the ExecuteSave method being called. protected virtual bool ClientCanSave(Type type) Override to control which types are authorized to be saved. This method is called from the base implementation of the AuthorizeSave. It may be overridden to add additional restrictions or to relax existing ones. If adding restrictions, make sure that the base implementation is called. protected virtual void OnError(Exception e, PersistenceFailure failureType) May be overridden to log errors. No logging is performed by the default implementation. Virtual Properties protected virtual bool DefaultAuthorization { get; } Override this property to change the Default authorization of whether or not authorization succeeds if no Authorization attributes are found. This property defines the default authorization behavior for any types that do not have a ClientCanSave attribute defined. The base implementation returns "true" which means that by default any type not otherwise restricted is saveable. By returning a "false" here, any types that are not specifically marked as saveable will restricted. Other properties In addition to the above, the following protected read only properties are also available. Property Property type Used for Principal IPrincipal The IPrincipal from the user session requesting this operation. EntityManager EntityManager Returns an EntityManager holding the entities to be saved. Note that this is not the original EntityManager on which the EntityManager.SaveChanges() call was made. This property can be very useful when overriding the ExecuteSave method. Additional entities that need to be saved can be added to this EntityManager or entities that should not be saved can be removed before calling base.ExecuteSave(). VerifierEngine VerifierEngine Returns the VerifierEngine which will be used for server-side validation. SaveOptions SaveOptions Returns the SaveOptions provided in the SaveChanges call. IsServerSave boolean Returns true if the save was issued on the server. This can occur as a result of an InvokeServerMethod call. This is useful because you typically do not need to reauthorize a save where the request for the save originates on the server. Note that the EntityManager returned by the EntityManager property is NOT the original client-side EntityManager on which the save request was made. It is a 'preauthenticated' server-side EntityManager, and its cache is consists of the entities being saved. Because of its "preauthenticated" nature, there is no Principal attached to this EntityManager and any operations that it performs bypass regular authentication. Because this EntityManager can only be accessed on the server from within code deployed to the server all operations that it performs are considered "priviledged". Because this EntityManager is untyped, additional entities of any entity type may queried with it. Queries against this EntityManager are usually composed via a technique shown in the following example, ( the code below is assumed to be executing within the context of some EntityManagerSaveInterceptor method). Note the use of the EntityQuery<T> constructor. C# var newQuery = new EntityQuery<Customer>().Where(c => c.CompanyName.StartsWith("S")) var custs = newQuery.With(EntityManager).ToList();
Handle cascaded deletes The ability to declaratively state that the delete of one entity should automatically cause the deletion of some its related "dependent" entities is a relatively common and very useful tool in the development of a complex domain model. In DevForce and in the Entity Framework these are called cascaded deletes and the Entity Framework Designer offers support for declaratively marking up an entity relationship as having a delete "cascade" rule that applies to it.
Telling EF about a cascaded delete Within the Entity Framework Designer, every relationship has its own property page that looks something like the following.
Cascaded deletes are configured by setting either of the "End1 OnDelete" or "End2 OnDelete" combo boxes to "Cascade". What this markup actually does in both DevForce and the Entity Framework (EF) is tell EF to expect that the "database" will perform its own form of cascaded delete for this relationship. i.e. that the database has some mechanism for insuring that when the records mapped to the parent entity are deleted that any "dependent" records will be automatically deleted as well. Let's call this a database "delete rule" for future reference. Who does what? To be clear, DevForce (and EF) does NOT take responsibility for cascading the delete in the database. DevForce does insure that the deletion of any principal entity within an entitymanager automatically forces the automatic deletion of its related entities also within the entity manager. Recall that deletion in this context is actually marking the entity for deletion so that it will actually be deleted from the database during the next SaveChanges call. In other words, DevForce will attempt to synchronize the EntityManager with the "expected" state of the database after a delete by understanding that the delete will cause side effect on the database. But... DevForce does not retrieve all the dependent entities when an entity is deleted and then issue deletes for them as well: It only deletes ( marks as deleted) any dependent entities that are already in memory. Restated, DevForce expects that deleting the principal entity in the database, will delete all its dependents in the database. So it makes, what should be, a redundant delete request for any dependent entities that it knows about, i.e. that are already loaded into the EntityManager's entity cache. The rule This leads a basic rules about cascaded deletes.
If you add an Cascade delete rule to the model, you MUST have a corresponding delete rulein the database. It is possible to break this rule if you are willing to insure that anytime that you delete a principal entity that all of its dependent entities have already been loaded into the EntityManager as well. We do NOT recommend this. A problem There is one interesting, rare, and annoying failure that can occur with this process. Assume that you have relationships that looks something like: Category > Product > Order with cascading deletes set up so that deleting a Category deletes its Products which in turn deletes its Orders. Then DevForce will, in some rare cases, be unable to synchronize an EntityManager with the database when you delete a Category. This can occur if you have an Order loaded that is related to a Category via an unloaded Product, and you delete the Category. Under these conditions, DevForce wont know to delete the Order. This means the Order will remain in the EntityManager in the unchanged state, despite it having been deleted in the database. Save a graph of entities When performing a save where you specify exactly which entities should be saved, it is often useful to be able to 'walk' a selected 'graph' of entities related to those that you know you are saving in order to determine if any other 'related' entities that need to be saved as well. This could be accomplished by 1) Iterating over each of the 'root' entities that you wish to save 2) For each of these entities iterating over each of the navigation properties that constitute your selected graph 3) For each of these navigation properties, perform the navigation, using a CacheOnly FetchStrategy to insure that no 'additional' entities are added to the local cache as a result of this operation. 4) For only those entities returned from the navigation that have an EntityState that is either Added, Modified or Deleted. 5) Accumulated these for our eventual result. 6) Repeat step 2 above for each these entities. But there is an easier way! The EntityManager.FindEntityGraph method does precisely this.
Using the FindEntityGraph method The EntityManager.FindEntityGraph method is specifically intended to be used to search the local cache for any entities that are navigable from a list of root entities according to a specified graph. C# public IList<Object> FindEntityGraph( IEnumerable roots, IEnumerable<EntitySpan> spans, EntityState entityState); VB public IList(Of Object) FindEntityGraph( IEnumerable roots, _ IEnumerable(Of EntitySpan) spans, _ EntityState entityState) Given any entity or entities in a graph, this method collects and retrieves all of the related entities in the graph. This is a cache-only operation. Parameter Description roots A list of entities used as the starting point for retrieving all other related entities. spans One or more EntitySpans; this defines the graph to be traversed. An error will be thown for any span that does not have a matching type in one of the roots. entityState The EntityState of entities to return. This allows you to filter by EntityState. An example of its use is shown below: C# // Get the root(s). var roots = listOfEmployeesToSave;
// Get the span(s). var spans = new List<EntitySpan> { new EntitySpan(typeof(Employee), EntityRelations.FK_OrderSummary_Employee, EntityRelations.FK_Order_Details_Orders, EntityRelations.FK_Order_Details_Products) };
// Find the related entities. var entityState = EntityState.Added | EntityState.Modified | EntityState.Deleted; var entities = myEntityManager_em1.FindEntityGraph(roots, spans, entityState); myEntityManager.SaveChanges(entities); VB ' Get the root(s). Dim roots = listOfEmployeesToSave
' Get the span(s). Dim spans = New List(Of EntitySpan) From {New EntitySpan(GetType(Employee), _ EntityRelations.FK_OrderSummary_Employee, EntityRelations.FK_Order_Details_Orders, _ EntityRelations.FK_Order_Details_Products)}
' Find the related entities. Dim entityState = entityState.Added Or entityState.Modified Or entityState.Deleted Dim entities = myEntityManager_em1.FindEntityGraph(roots, spans, entityState) myEntityManager.SaveChanges(entities) But the roots do not all have to be of the same type. For example: C# // Add root(s). var roots = new List<Entity> { emp, supplier };
// Add span(s). var spans = new List<EntitySpan> { new EntitySpan(typeof(Employee), EntityRelations.FK_OrderSummary_Employee, EntityRelations.FK_Order_Details_Orders), new EntitySpan(typeof(Supplier), EntityRelations.FK_Products_Suppliers, EntityRelations.FK_Products_Categories) };
// Find the related entities. var entityState = EntityState.Added | EntityState.Modified | EntityState.Deleted; var entities = myEntityManager_em1.FindEntityGraph(roots, spans, entityState); myEntityManager.SaveChanges(entities); VB ' Add root(s). Dim roots = New List(Of Entity) From {emp, Supplier}
' Add span(s). Dim spans = New List(Of EntitySpan) From {New EntitySpan(GetType(Employee), _ EntityRelations.FK_OrderSummary_Employee, EntityRelations.FK_Order_Details_Orders), _ New EntitySpan(GetType(Supplier), EntityRelations.FK_Products_Suppliers, _ EntityRelations.FK_Products_Categories)}
' Find the related entities. Dim entityState = entityState.Added Or entityState.Modified Or entityState.Deleted Dim entities = myEntityManager_em1.FindEntityGraph(roots, spans, entityState) myEntityManager.SaveChanges(entities)
Secure Last modified on March 16, 2011 11:58 Every application, whether a 2-tier or n-tier desktop application used only within an organization or an ASP.NET or Silverlight application accessible across the internet, must consider its threat exposure. Consider the following: Any client-side code, including Silverlight applications, can be easily disassembled (e.g., using Reflector or Silverlight Spy) All messages to and from a server can be easily intercepted and monitored (e.g., using Fiddler) If we assume that both the client and the transport are exposed and can be compromised, then we need to consider the risks to the application, its users, and the organization. Does your application contain sensitive information which should be restricted to certain users? Does it contain personal information such as an individual's health record or financial data? Are only paid users allowed access? If guest users are permitted, what data and operations can they access? Silverlight applications have additional challenges. Because its often used to create public- facing applications, Silverlight is inherently more dangerous than in-house applications that run behind a firewall or over a VPN. Will your Silverlight application run out-of-the- browser? Will it use Isolated Storage? Will it access services on other domains? As a developer you must ask all these questions, and many more, as you build and secure your application. In the following sections we'll look at how DevForce can help in authenticating and authorizing users, and other actions you can take to build a secure application. In the following topics we'll discuss various aspects of securing your application: Authentication - the process of validating a user's identity Authorization - how to ensure the user accesses only the information allowed Additional steps - additional steps you can take in locking down your application Additional Resources Since we can't cover every aspect of security here, we encourage you to learn more about this topic. Here are a few links to get started: Silverlight Security MSDN article Ward Bell's DevConnections 2010 slides on Silverlight security See just how easy it is to eavesdrop on HTTPS traffic with Fiddler Authenticate Last modified on March 16, 2011 13:19 Authentication is the act of confirming that users are who they claim to be. Authenticating a claim might be done using ASP.NET security, Active Directory, Windows Identity Foundation, or a custom implementation. DevForce has built-in hooks to support the authentication scheme of your choice, and includes out-of-the-box support for ASP.NET security. Unless your application allows "guest" access, when your application starts you should obtain user credentials. These might be in the form of a userid and password (when using ASP.NET security this is called Forms authentication), or it might mean obtaining an "ambient" credential, such as the current Windows userid or HttpContext.Current.User. In DevForce, an EntityManager must be "logged in" to the EntityServer in order to access data or services. As a developer you have control over how and when this login occurs. You determine the credentials your application accepts, you call a Login method to authenticate the user, and you write the server code to perform the authentication via the IEntityLoginManager. When you determine a user is authenticated, you provide that information to DevForce in the form of an IPrincipal. If you're not familiar with the IPrincipal interface, it, along with the IIdentity interface, is a flexible means of holding information on who a user is and their roles. It does not hold user credentials or claims, but does indicate how the user was authenticated. The DevForce implementation of these interfaces is the UserBase. On the server DevForce will tuck this IPrincipal away, and return a token called a SessionBundle to the client. The SessionBundle contains an encrypted credential, encrypted using the EntityServer's specific "session encryption key". The SessionBundle is transmitted with every request to the server, where DevForce first validates the bundle before proceeding with the request. In Azure and other load balanced environments, the encrypted credential in the bundle allows the user to be authenticated by any server having the same session encryption key. (This key is configurable and we'll see more on this key later.) Once logged in, the server keeps track of each user's last access, and will time the session out after a configurable period of inactivity. Guest Users A guest user is an anonymous user -- one who has not provided a credential and is therefore not authenticated. We noted above that an EntityManager must be logged in to an EntityServer, and that every request to the server is accompanied by a token which is validated before proceeding. How does this work with anonymous users? If you do not call the EntityManager Login method and supply a credential, DevForce will do it for you when necessary. DevForce first determines whether you have disabled anonymous access via a configuration setting; if not, a UserBase is constructed for the guest user indicating the user is not authenticated and has no assigned roles. The DevForce default is to allow anonymous users, but it is a very good idea to disable this feature unless your application intentionally allows it. When anonymous logins are disabled, an exception is thrown for the Login attempt, and no further access to the EntityServer is permitted from the EntityManager. To disable guest access, set the AllowAnonymousLogin flag to false in the web.config or server .config file: XML <objectServer> <serverSettings allowAnonymousLogin="false" /> </objectServer>
The Login process The process of logging in a user involves a handshake between the Entity Manager and the Entity Server. The EntityManager Login method is used to supply a client credential to the server in the form of an ILoginCredential. The server in turn tries to authenticate the user via an implementation of the IEntityLoginManager, and returns user information back to the EntityManager in the form of an IPrincipal. All these interfaces may seem confusing, but they mean that you the developer are in control of the login process.
On the client Authentication begins on the client, when Login is called. Until a Login successfully completes, the EntityManager may not communicate with the EntityServer for data or services. Login At its simplest, gather user credentials and call the EntityManager Login method (only the asynchronous version is available to Silverlight applications): C# bool Login(ILoginCredential credential);
LoginOperation LoginAsync(ILoginCredential credential) It's worth noting that the credential is passed in clear text. Use a secure channel (such as SSL) or provide your own encryption of the credential data if secure communications are required. What if you don't have a credential? For example, you're using Windows credentials, or allow guest access, or you're using ASP.NET security with Windows authentication or a persistent or ambient authentication ticket. In these cases, calling the Login method with a null credential indicates that the EntityServer will determine how to handle the login request. When using ASP.NET security, the built-in implementation will try to use the current user information from the HttpContext. After the EntityManager is logged in, you can access the returned IPrincipal through the EntityManager.Principal property. If the Login failed, a LoginException is returned. When called synchronously the exception is thrown; when called asynchronously the exception is available in the LoginOperation.Error property. Logout The EntityManager Logout method performs another handshake with the EntityServer, telling it to remove session information for the user. Logout also clears the IPrincipal for the EntityManager, and further requests to the server from the EntityManager are disallowed until it again logs in. Many applications do not explicitly call Logout, and it's not strictly necessary. The user session information on the server will expire after a period of inactivity. If your application is available on public computers it's generally a good idea to logout either by user request or when the application closes. (Note that the user session information stored on the server has a very small footprint. It's simply the SessionBundle containing encrypted credentials and a few additional fields tracking last activity; no other data is stored.) On the server DevForce supplies built-in login processing on the server if using ASP.NET security. When not using ASP.NET security you must provide a custom implementation of the IEntityLoginManager interface, otherwise all users will be logged in as "guest" users. Implementing IEntityLoginManager The IEntityLoginManager is responsible for verifying a user's identity. Add a class which implements the interfaces methods, and ensure that DevForce can find the class by placing the assembly in the same folder as the executable or bin folder. The assembly needs to be deployed only on the server, since login authentication processing is not performed on the client. The interface prescribes two methods: Login and Logout. Login Your Login method will be passed the ILoginCredential that the client used in the EntityManager.Login call. Note 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 EntityServer and does not require a login; it is not the same EntityManager from which you called Login on the client. This EntityManager is not an instance of your sub-typed domain-specific EntityManager either, but rather of the base EntityManager class. From your Login method, you should return a type implementing IPrincipal. Common implementations are GenericPrincipal, WindowsPrincipal, or UserBase; but any serializable type is allowed. Neither GenericPrincipal nor WindowsPrincipal is available in Silverlight applications, but you can use the DevForce UserBase class or implement a custom IPrincipal. If the credentials supplied are not valid, you should throw a LoginException indicating the cause of the failure. 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 Heres a sample class implementing the IEntityLoginManager interface. It returns a UserBase from the Login method, and requires no special Logout processing. Note that this sample performs no actual authentication of the user credentials, and should therefore not be used in a real application! It also throws an exception if a credential was not supplied. The sample is intended only to show the simple mechanics of the interface. C# public class LoginManager : IEntityLoginManager { public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) { if (credential == null) { throw new LoginException(LoginExceptionType.NoCredentials, "No credentials supplied"); } // Return an IPrincipal. Any serializable type may be returned, // here we use the DevForce UserIdentity and UserBase classes. var identity = new UserIdentity(credential.UserName, "Custom", true); var principal = new UserBase(identity); return principal; }
public void Logout(IPrincipal principal, EntityManager entityManager) { // No special processing needed. } } VB Public Class LoginManager Implements IEntityLoginManager Public Function Login(ByVal credential As ILoginCredential, _ ByVal entityManager_Renamed As EntityManager) As IPrincipal If credential Is Nothing Then Throw New LoginException(LoginExceptionType.NoCredentials, _ "No credentials supplied") End If ' Return an IPrincipal. Any serializable type may be returned, ' here we use the DevForce UserIdentity and UserBase classes. Dim identity = New UserIdentity(credential.UserName, "Custom", True) Dim principal = New UserBase(identity) Return principal End Function
Public Sub Logout(ByVal principal As IPrincipal, _ ByVal entityManager_Renamed As EntityManager) ' No special processing needed. End Sub End Class Ensuring that Login will fail if an IEntityLoginManager class is not deployed To ensure that your custom IEntityLoginManager is found and used, you can set a configuration option which will cause DevForce to throw an exception when not found. By default the EntityServer will resort to it's own authentication processing when a custom implementation is not found and ASP.NET security is not in use: that processing ignores supplied credentials and logs all users in as guests. This is rarely what your applications wants, except in those rare cases where only anonymous access is provided in the application. If you've supplied an IEntityLoginManager and you want to ensure that it is in fact being used, set the LoginManagerRequired flag in the web.config or server .config to true: XML <objectServer> <serverSettings loginManagerRequired="true" /> </objectServer> Customizing the Login process DevForce will usually find your custom implementations without any extra work on your part. See the topic on extending DevForce for more information on how to ensure your custom types are discovered. ILoginCredential Any serializable type implementing the ILoginCredential interface can be used. The LoginCredential, and the FormsAuthenticationLoginCredential are the DevForce- supplied implementations. The credential supplied in the EntityManager.Login call is the credential received in the IEntityLoginManager.Login. The class defining the ILoginCredential must be available on both client and server. IPrincipal Any serializable type implementing System.Security.Principal.IPrincipal can be returned from the IEntityLoginManager.Login method. The object is returned to the client and is available via the EntityManager.Principal property on the EntityManager instance on which Login was called. The IPrincipal is also available to other server methods. The class defining the IPrincipal must be available on both client and server. The IIdentity held by the IPrincipal must also be a serializable type and available on both client and server. If you sub-type either UserBase or UserIdentity, the DevForce default implementations of IPrincipal and IIdentity, your classes must be: 1. Marked with the DataContract attribute. Also be sure to mark any custom properties which should be transmitted between tiers with the DataMember attribute. 2. Included on both the client and server! You won't be able to login if your custom classes aren't defined in assemblies deployed to both client and server tiers. LoginException. Any serializable type extending LoginException can be thrown for login failures. To ensure that your custom exception is received correctly on the client, you must also implement a constructor accepting the message, and a constructor accepting a dictionary of any custom properties. DevForce will automatically serialize any custom properties via a Dictionary<string, object>, and will call the appropriate constructor when building the exception on the client. The class defining the custom exception must be available on both client and server. For example: C# [DataContract] public class CustomLoginException : LoginException { public CustomLoginException(string message, int severity) : base(message) { Severity = severity; }
public CustomLoginException(string message, Dictionary<string, object> userData) : base(message) { Severity = (int)userData["Severity"]; } [DataMember] public int Severity { get; internal set; } } VB <DataContract()> _ Public Class CustomLoginException Inherits LoginException Public Sub New(ByVal message As String, ByVal severity As Integer) MyBase.New(message) Me.Severity = severity End Sub
Public Sub New(ByVal message As String, _ ByVal userData As Dictionary(Of String, Object)) MyBase.New(message) Severity = CInt(Fix(userData("Severity"))) End Sub
Private privateSeverity As Integer <DataMember()> _ Public Property Severity() As Integer Get Return privateSeverity End Get Friend Set(ByVal value As Integer) privateSeverity = value End Set End Property End Class How to authenticate without logging in Now that we've covered all the intricacies of the login process, it's time to cover how the EntityManager can be authenticated without ever calling Login. There are several ways, and they're all based on sharing an existing credential - well, it's not truly the credential the user supplied, since that is discarded after a login, it's the SessionBundle, the token representing the user. Create an EntityManager via the copy constructor C# var newEntityManager = new EntityManager(anotherEntityManager); VB Dim newEntityManager = New EntityManager(anotherEntityManager) The new EntityManager will have the same connection state, and share the user information from the other EntityManager. Use EntityManager.LinkForAuthentication C# var entityManager = new EntityManager(); entityManager.LinkForAuthentication(anotherEntityManager); VB Dim entityManager = New EntityManager() entityManager.LinkForAuthentication(anotherEntityManager) ) Use I AuthenticationManager.LinkAuthentication C# bool LinkAuthentication(EntityManager targetEM); VB Boolean LinkAuthentication(EntityManager targetEM) ) The EntityManager will automatically search for an implementation of IdeaBlade.EntityModel.IAuthenticationManager and call its LinkAuthentication method if it's not already logged in and any access to the EntityServer is needed. A sample IdeaBlade.EntityModel.IAuthenticationManager implementation is provided in the Business Application templates.
Use ASP.NET security DevForce provides out-of-the-box integration with ASP.NET security features (Membership, Roles, Profile). These features can be used in any application -- Silverlight, desktop, or ASP.NET. If developing for Silverlight or ASP.NET, choosing ASP.NET security is often the right choice over a custom security implementation. ASP.NET security is easy to use, highly configurable, and well-documented. See http://www.asp.net/learn/security/ for information on ASP.NET security features. For Silverlight applications, DevForce provides Visual Studio project templates for a "DevForce Silverlight Business Application" which automatically use ASP.NET security features without any additional work or configuration required on your part. See the Business Application Templates for more information. In order to use ASP.NET security in DevForce you must set the UseAspNetSecurityServices flag in the web.config or server .config to enable it. When enabled, DevForce will use the AspAuthenticatingLoginManager to handle login requests from clients. XML <objectServer> <serverSettings useAspNetSecurityServices="true" /> </objectServer> You must also enable AspNetCompatibility in order to allow the DevForce services to integrate with ASP.NET services. You set this in the system.serviceModel configuration section. Here's the relevant element in the system.serviceModel section: XML <system.serviceModel> <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 in ASP.NET can take either of two flavors Forms or Windows. For either type of authentication, after a successful Login completes a UserBase instance representing the user is available on both client and server. Forms Authentication Forms authentication involves validating user credentials against a Membership provider. The default provider uses a SQL Server Membership database, aspnetdb, to store user information. ASP.NET also supplies a Membership provider for Active Directory, or you can write a custom provider. To use Forms authentication, specify this authentication mode in the system.web configuration section: XML <system.web> <authentication mode="Forms" /> </system.web> In your application you can ask the user for login credentials and pass the credential in the EntityManager.Login call. DevForce will 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. You can also call EntityManager.Login with a null argument if your application accepts either persistent authentication tickets or the user has already logged in as part of the larger application. Windows Authentication When using Windows authentication in ASP.NET the current Windows credentials of the client are transmitted to the server. This can be used in intranet environments only. To use Windows authentication, specify this authentication mode in the system.web configuration section: XML <system.web> <authentication mode="Windows" /> </system.web> Note that additional configuration changes are required both in IIS and in the communications configuration in order to pass Windows credentials to the EntityServer. These changes are discussed in the configuration topic. On your client, call the EntityManager.Login method with a null credential. The AspNetAuthenticatingLoginManager will check the HttpContext.Current.User for a WindowsPrincipal representing the user, and from that create a UserBase to be returned to the client. Roles You must enable the Role service in the configuration file in order to use this feature: XML <system.web> <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, and define the profile properties wanted. Here's a sample: XML <system.web> <profile enabled="true"> <properties> <!-- Sample properties --> <add name="WindowSeat" type="bool" defaultValue="false" /> <add name="Building" type="string" defaultValue="A" /> </properties> </profile> </system.web> You also need to extend the UserBase class with the custom properties from your profile. DevForce will automatically populate these properties from the Profile if the property name and type match, and the setter is public. Your custom UserBase class must be serializable, since it will be transmitted between client and server tiers. The DevForce Business Application project template for Visual Studio uses the Profile service and shows a sample User class which extends the UserBase with a Profile property. Customizations Credentials (Forms authentication) You can pass custom credentials, derived from ILoginCredential, LoginCredential, or FormsAuthenticationLoginCredential with the Login call. With custom credentials, you will generally also want to provide a custom IEntityLoginManager implementation to receive these credentials. If you wish to take advantage of existing DevForce ASP.NET service integration, you should derive your class from the AspNetAuthenticatingLoginManager and override methods as needed. IEntityLoginManager You can implement your own IEntityLoginManager or extend the AspNetAuthenticatingLoginManager to provide custom logic. Any custom implementation will be used if found. UserBase You can also extend the UserBase class. If you enable the ASP.NET Profile service you will want to use a custom UserBase which contains additional properties retrieved from the profile. DevForce will automatically return your custom UserBase (if found) without the need to implement a custom AspNetAuthenticatingLoginManager. Troubleshooting "Error using ASP.NET Membership: Unable to connect to SQL Server database." Message received on a Login 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, remove the old connection string and add the new connection string. Here's a sample re-pointing the connection to the default instance of SQL Server on the machine: XML <connectionStrings> <remove name="LocalSqlServer" /> <add name="LocalSqlServer" connectionString= "Data Source=.;Initial Catalog=aspnetdb;Integrated Security=True;" providerName="System.Data.SqlClient" /> </connectionStrings>
Security in the Busines Application Template To facilitate building secure applications in Silverlight, DevForce provides the Business Application Visual Studio project templates. These templates provide the same navigation structure and themes as in standard Silverlight navigation and business templates, but integrate with DevForce ASP.NET security features. Both C# and VB versions are available.
Overview These templates provide both user authentication and registration using ASP.NET Membership, and include sample use of ASP.NET Role and Profile features. The templates also show how "single sign-on" can be performed in DevForce. Automatic sign-on When an application built from the templates starts, the first thing it does is create an AuthenticationManager. This is a sample implementation of the IdeaBlade.EntityModel.IAuthenticationManager interface, and provides features to login and register users. When the application starts it calls AuthenticationManager.LoadUser to automatically login the user if the application is using Windows authentication, or if the user has a persistent authentication ticket (the "Keep me signed in" checkbox was checked). If authenticated, the user's "friendly name" is displayed in the main window. This "friendly name" is an ASP.NET Profile property. You'll see it defined in both the web.config: XML <profile> <properties> <add name="FriendlyName" /> </properties> </profile> ... and in the custom User class which extends the default UserBase: C# [DataContract] public partial class User : UserBase {
/// <summary> /// Gets and sets the friendly name of the user. /// </summary> /// <remarks> /// This is a Profile-backed property. /// </remarks> [DataMember] public string FriendlyName { get; set; } .. } VB <DataContract> Partial Public Class User Inherits UserBase
''' <summary> ''' Gets and sets the friendly name of the user. ''' </summary> ''' <remarks> ''' This is a Profile-backed property. ''' </remarks> <DataMember> Public Property FriendlyName() As String Private ... End Class If the user could not be authenticated, then she's logged in as a "guest user", and can either login or register at any time. Login The templates provide Login processing - a window to gather credentials, and logic to submit those credentials to the EntityServer for authentication. On the server the standard DevForce AspAuthenticatingLoginManager will authenticate the credentials and return a User instance. Here's the Login window (in the Windows 7 theme):
Register New users may also register. A Registration window allows the user to enter information, and the template provides the logic to register the user to ASP.NET Membership. Validation of user-entered information is also provided. Here's the Registration window (also in the Windows 7 theme):
Single sign-on Once a user is logged in via the AuthenticationManager, any EntityManager subsequently created will automatically use the credentials already obtained by the AuthenticationManager. The credentials aren't actually resubmitted to the EntityServer, but each EntityManager will use the AuthenticationManager.LinkAuthentication method to obtain the existing User information, and will not need to call the EntityManager.Login method.
Authorize To build a secure application, it's not enough to only authenticate our users: we must still authorize their access to data and services. Your application may support users with different roles - for example 'Admin' users may have full privileges, while 'HR' users may only work with information in their department. But you must also authorize against rogue or malicious users, since in most environments you must assume that either the client or the transport can be compromised. DevForce provides role-based security authorization features to help. Before we dive into a discussion of when and how authorization can be used, let's briefly review the IPrincipal/IIdentity means of providing role-based authorization. Once logged in, by default a UserBase (or some custom IPrincipal implementation) is always available to all client and server code. On the client, the Thread.CurrentPrincipal is set to the logged in user (except in Silverlight), and the EntityManager.Principal property will return the logged in user. On the server, the Thread.CurrentPrincipal is also set for the calling user, and all methods and base classes provide either a Principal argument or property. Having the principal always available means that both declarative and programmatic authorization can be performed. We discuss the attributes available for declarative authorization below, but it's also very easy to programmatically check authorization. For example, a query or save could include a simple check such as the following: C# bool isAuthorized = Principal.IsInRole("admin"); VB Boolean isAuthorized = Principal.IsInRole("admin") Entity-level authorization When you created your entity model, you may have noticed in the EDM Designer that each entity type has DevForce properties that govern the ability of the client to query and save:
The CanQuery and CanSave properties translate to ClientCanQuery and ClientCanSave attributes on the generated Entity class. The "Allow includes" and "Allow projects" properties combine to determine a ClientQueryPermissions attribute. Suppose we kept the default values for the two "Allow ..." properties and disabled (made false) the client's ability to query or save this type. The generated class would look like this: C# [IbEm.ClientCanQuery(false)] [IbEm.ClientCanSave(false)] public partial class OrderDetail : IbEm.Entity {} VB <IbEm.ClientCanQuery(False), IbEm.ClientCanSave(False)> Partial Public Class OrderDetail Inherits IbEm.Entity End Class Turning off all direct query access may seem a bit draconian but you may have types that should really only be accessible on the server. Turning off all direct save access can be particularly useful, since many models have quite a bit of read-only data. Why would you set CanQuery or CanSave to true? To override the default, which will be determined by the implementation of the "interceptor", EntityServerQueryInterceptor or EntityServerSaveInterceptor. (We'll discuss the interceptors in more detail below.) The default implementations of the interceptors allow all queries and saves, but if you've implemented custom interceptors you might find it useful to disable all access by default, and enable it only for entity types as needed. Remember that each entity is defined as a partial class, making it easy to specify these attributes with code rather than in the designer. In the partial class you can be more particular and assign the permissions by role as seen in this example. C# [ClientCanQuery(AuthorizeRolesMode.Any, "Admin", "Sales")] public partial class OrderDetail : IbEm.Entity {} VB <ClientCanQuery(AuthorizeRolesMode.Any, "Admin", "Sales")> Partial Public Class OrderDetail Inherits IbEm.Entity End Class You may also decorate your entities with the RequiresAuthentication and RequiresRoles attributes. The default interceptors, when authorizing a query or save, will check these attributes before checking the ClientCanQuery and ClientCanSave attributes. RequiresAuthentication can be used to ensure that a guest user does not have any access to the entity, while RequiresRoles functions similarly to ClientCanQuery and ClientCanSave when used to require the user be a member of all roles specified. C# [RequiresAuthentication] public partial class OrderDetail : IbEm.Entity {} VB <RequiresAuthentication> Partial Public Class OrderDetail Inherits IbEm.Entity End Class A separate topic devoted to securing the query with attributes covers this approach in greater detail. Query authorization The EntityServerQueryInterceptor, discussed in depth in the query life cycle topic, is responsible for authorizing data retrieval. The interceptor allows you to authorize query access, add additional filters to a query, and to authorize the query results. As we saw above, the default interceptor will authorize every query using the authorization attributes decorating the entity. To customize this behavior, and add additional authorization, you'll need to create a custom interceptor sub-classing the EntityServerQueryInterceptor. In your custom interceptor you'll have a chance to: Override the default authorization - You can disable all retrieval unless specifically enabled for an entity or query. Perform programmatic authorization - The interceptor Principal property returns the IPrincipal from the Login and can be used to check roles, and additional information if a custom IPrincipal is used. Add filters to the query - You can inspect and modify the query prior to execution. For example, maybe a "UK" user can query orders from her country only - here's the place to add that logic. Authorize query results - Once the query has executed you have one last chance to look through the results to determine if the user is authorized to see them. Through a custom interceptor you can also perform additional logging to audit user access and capture error details. Save authorization The EntityServerSaveInterceptor is responsible for authorizing modifications to data before they are committed to the data source. The interceptor is discussed in depth in the save life cycle topic. As we saw above in the discussion of entity-level authorization, the default interceptor will authorize every save using the authorization attributes decorating the entity. To customize this behavior, and add additional authorization, you'll need to create a custom interceptor sub- classing the EntityServerSaveInterceptor. In your custom interceptor you'll have a chance to: Override the default authorization - You can disable all saves unless specifically enabled for an entity. Perform programmatic authorization - The interceptor Principal property returns the IPrincipal from the Login and can be used to check roles, and additional information if a custom IPrincipal is used. Customize validation - Instance validation is performed by default, but you can fine tune the validation performed, and add additional verifiers not included on the client. Through a custom interceptor you can also perform additional logging to audit user access and capture error details. Field-level authorization You might need to authorize user access to specific properties on an entity. Maybe all your users have read access to the Employee.Name property, but only users with the "Admin" role can modify it. There are several techniques within DevForce to control field-level (property- level) access to data. Scrub the queried data - You can override the ExecuteQuery method in the EntityServerQueryInterceptor to "scrub" the queried data before it is sent to the client. Note you can use this technique only for read-only data, since any modifications to the entity will cause the scrubbed result to be written to the database. For example: C# protected override bool ExecuteQuery() { bool ok = base.ExecuteQuery(); if (ok) { if (!this.Principal.IsInRole("Admin")) { if (Query.ElementType == typeof(Customer)) { foreach (var customer in this.QueriedEntities.Cast<Customer>()) { customer.CreditLimit = "***"; } } } } return ok; } VB Protected Overrides Function ExecuteQuery() As Boolean Dim ok As Boolean = MyBase.ExecuteQuery() If ok Then If Not Me.Principal.IsInRole("Admin") Then If Query.ElementType = GetType(Customer) Then For Each customer As var In Me.QueriedEntities.Cast(Of Customer)() customer.CreditLimit = "***" Next End If End If End If Return ok End Function Use property interceptors - You can define an interceptor to be called before or after a get or set, and you can define multiple chained interceptor actions. You can easily define interceptors via attributes, and you can create them programmatically. Within an interceptor you can check the EntityManager.Principal to determine the user's identity and roles. You can also log details on property access. Property interception is generally a client- side activity, since it's there that most property-level access and changes are performed, as such, don't overly rely on your property interceptors as a security mechanism. See the Property interceptors topic for detailed information. Customize validation - Validation of changes on the server is critical if you require authorization of property-level changes. Remember that on the server you can add verifiers not included on the client. Return part of an entity - Not a true security feature, and like property interceptors a client- side feature which malicious users can abuse, query projections (either anonymous or into a type) are a means of limiting a query to only the select properties. Model new entity types - In your entity model, use views, inheritance, entity splitting and other modeling features to define entities requiring special access requirements. Maybe you'll need an Employee entity and a SecureEmployee entity; in doing so you can determine the properties in each, perform custom validations, and control query and save authorization by entity type.
RSM authorization You can and should add authorization checks to your remote server methods too. The RequiresRoles and RequiresAuthentication attributes can be used on remote methods, and you can add programmatic authorization. Let's look at a sample method: C# [AllowRpc, RequiresRoles("admin")] public static Object GetNumberOfOrders(IPrincipal principal, EntityManager entityManager, params Object[] args) { ... } VB <AllowRpc(), RequiresRoles("admin")> _ Public Shared Function GetNumberOfOrders(ByVal principal As IPrincipal, ByVal entityManager As EntityManager, ByVal ParamArray args As Object()) As Object ... End Function
Here we've added a RequiresRoles attribute to ensure that only users with the "admin" role will be able to invoke the method. But notice the principal argument to the method? We could just as easily use that principal to perform authorization, as shown below. C# [AllowRpc] public static Object GetNumberOfOrders(IPrincipal principal, EntityManager entityManager, params Object[] args) { if (!principal.IsInRole("admin")) { throw new PersistenceSecurityException("Access denied"); } .. } VB <AllowRpc()> _ Public Shared Function GetNumberOfOrders(principal As IPrincipal, entityManager As EntityManager, ParamArray args As Object()) As Object If Not principal.IsInRole("admin") Then Throw New PersistenceSecurityException("Access denied") End If ... End Function POCO authorization Both declarative and programmatic authorization can be performed with POCO types. All POCO CRUD methods in a service provider class - for query, insert, update and delete - can be decorated with the RequiresRoles or RequiresAuthentication attributes to control access to the method. You can also check the Thread.CurrentPrincipal directly to add programmatic authorization checks to these methods. If using a custom EntityServerPocoSaveAdapter, you can add programmatic authorization to the BeforeSave, InsertEntity, UpdateEntity and DeleteEntity methods. See the POCO topic for more information.
Additional steps We've covered the basics of authentication and authorization previously, but there are still additional steps you can take in building and deploying a secure application. We've broken these down into configuration-related opportunities - considerations which can be addressed through the deployment configuration; and coding-relating - additional choices you can make in your code.
Configuration Use SSL to secure communications. The default DevForce configuration will automatically use a secure transport if you specify a URL beginning with https in the objectServer tag. If your Silverlight application was downloaded via a URL beginning with https, then communications to the EntityServer will use a secure transport. See here for more information on configuring SSL in IIS. Disallow access to the debug log file. In IIS, unless you restrict access, anyone can browse to your debuglog.xml file and discover much more about your application than you probably want to share. To see if your debug log is browsable, just point your internet browser to it - for example http://localhost/badgolf/log/debuglog.xml. Blocking access is easy and there are many ways to do it; here are a few options: 1) change the logFile path to a secure folder outside of the IIS application folder, 2) turn off all NTFS permissions to the log folder except as required by the application pool account to create and append to the file, or 3) disable all IIS authentications for the folder. In a client application, you may want to turn off the debug log altogether if it's not possible to secure the folder. Use a database account with appropriate privileges. The userid defined in the database connection string should not be an administrator or DBA account. Protect the configuration file. You can encrypt configuration sections, such as the connectionStrings and ideablade.configuration sections, to limit unauthorized access. See here for more information. (To encrypt the ideablade.configuration section, make sure that the section definition contains the fully-qualified assembly name.) Consider encrypting database connections. See here for more information on encrypted SQL Server connections. Do not publish service metadata. By default metadata publishing is disabled for DevForce services. If you enabled it for any reason, be sure to turn it off when you deploy your application. Disable anonymous access in IIS if only authenticated users may access your application. Do not use a "wide open" client access policy. If you do not support cross domain access, remove the clientaccesspolicy.xml and/or crossdomain.xml files. If cross domain access is needed, lock down the access granted. Note that the sample file provided by DevForce is "wide open" and is intended as a starter sample only. Coding Send less detailed error messages to the client. Although during development you want detailed error messages to help in diagnosing problems, your client applications should not receive error messages from the server which might contain detailed or sensitive information. Implement a custom EntityServerErrorInterceptor to examine and optionally replace the exception to be sent to the client. Do not store connection strings in a client config file. If the application is an n-tier application the connection information is unnecessary, as only the EntityServer requires connection information. In a 2-tier application, consider using a custom DataSourceKeyResolver, which although not completely secure because your code can be disassembled, may still be more secure than the .config file. Encrypt sensitive files. If you save an EntityCacheState or other sensitive information to the file system or Isolated Storage, consider encrypting it. Here's a sample. Consider logging for audit purposes. Consider the entity model exposure. The client application can be easily disassembled to see the entire generated entity model. Do you really want that User entity exposed? If you are using custom authentication with your own user database, consider not including these tables in your entity model, or creating a separate server-only entity model just for authentication purposes. Consider WCF security. Depending on the URL, DevForce will default to either non- secure communications or transport security with SSL. For additional security requirements you can configure both client and service security using configuration file settings, or by implementing custom ServiceHostEvents and ServiceProxyEvents classes. Intercept server exceptions In production, your client application should not receive error messages from the server which might contain detailed or sensitive information. You can intercept server exceptions to examine and optionally replace the exception to be sent to the client. EntityServerErrorInterceptor Implement a custom EntityServerErrorInterceptor to intercept exceptions thrown on the server. With the OnError method of the interceptor you'll have an opportunity to inspect all exceptions - thrown by login, queries, saves, and unhandled exceptions from RPC methods. The default interceptor writes a trace message of the the full exception text to the log, but does not modify the original exception. To avoid sending sensitive information to a client application - for example, when a save fails it might indicate the database table or user - you can replace the exception to be returned to the client. Note that you can't entirely "swallow" the exception and return no error to the client. If you set the exception to null DevForce will send the original exception back to the client. The error interceptor is easy to implement, simply sub-class the base EntityServerErrorInterceptor and override the OnError method. Be sure to place your class in an assembly deployed to the server. Here's a simple example: C# using System; using IdeaBlade.Core; using IdeaBlade.EntityModel; using IdeaBlade.EntityModel.Server;
namespace Samples {
class SampleErrorInterceptor : EntityServerErrorInterceptor {
public override void OnError(ErrorInterceptorArgs args) {
// Log the real exception TraceFns.WriteLine(string.Format("Error {0} on thread {1} for user {2}", args.Exception.ToString(), System.Threading.Thread.CurrentThread.ManagedThreadId, base.Principal.Identity.Name));
// Add more detail if possible: var ese = args.Exception as EntityServerException; if (ese != null) { TraceFns.WriteLine(string.Format("Failure type: {0}, operation type: {1}", ese.FailureType, ese.OperationType)); }
// If the user is a guest, then scrub the exception. if (!base.Principal.Identity.IsAuthenticated) { args.Exception = new ApplicationException("The request could not be completed."); } } } } The exception will be wrapped in an EntityServerException when returned to the client, with the RemoteException information describing your exception. Because the actual exception you throw is not returned to the client application, you can return any exception type, even it that type is not defined on the client or is not serializable. Remember that if you do scrub the exception, the try/catch logic in your client code should be able to handle it. Test, log and debug
Test Last modified on April 28, 2011 17:14 Contents Testing features in DevForce o Test data o Mocks and fakes o Data source extensions Additional resources A discussion on development isn't complete without a mention of testing. We focus here primarily on unit and integration testing - activities often performed by the developer.
We know that a lot of you still aren't writing unit tests. You say it's too hard, or you don't have time. It's no longer hard, since all but express versions of Visual Studio 2010 now contain built-in unit testing tools, and if you're not happy with those there are free 3rd party tools such as NUnit, xUnit, and more. Surely you can find a test framework to suit you. But you don't have the time? Yet you do have time to spend countless hours wading through your UI and code to isolate a single failing line of code, which could have been easily found and remedied, or maybe even avoided, if unit tests existed. We'll offer two pieces of advice: Design with testability in mind - Are you using MVVM? If so, then creating unit tests for your view models is easy. Using the Repository pattern? Then a test or fake repository can help. Write unit tests as you go - Writing unit tests for new code is much easier than trying to add the tests later. Code coverage will be better, and tooling in Visual Studio 2010 makes creating test projects and tests easier. Testing features in DevForce DevForce can help make testing easier. Test data Unit tests shouldn't go to the database, and you don't want to fire up IIS for your EntityServer, yet you want data for your testing. You also want to refresh the data easily so that the tests don't corrupt it. Most of the techniques for creating design time data also apply to unit tests. The EntityManager can be used in "offline" mode - created without connecting to the EntityServer, and the DefaultQueryStrategy set to CacheOnly so that no queries are sent to the server. Data can be loaded from a previously created EntityCacheState; or simple entities created, and then added or attached to the EntityManager. Your tests can then run queries and perform property navigation, without requiring a database or potentially asynchronous service calls. Mocks and fakes DevForce provides contextual discovery via the CompositionContext, which is particularly useful in creating custom fakes and mocks for testing. Using CompositionContext.Fake and the EntityServerFakeBackingStore, you can test much of your code without ever touching a database. Data source extensions When you're ready to test against a database, you can dynamically target different databases using data source extensions. You likely use one database during development, and other databases in your staging, test and production environments. Data source extensions allow you to easily switch among these databases depending on the environment. Additional resources Ward Bell on "The Art of Unit Testing" Patterns and practices - Many of our patterns and practices for DevForce help with building more testable components. MSDN documentation on creating unit tests Log Last modified on March 29, 2011 17:08 Contents The Debug log o The <logging> element in more detail Generate custom messages DevForce always generates tracing messages as it runs, on both client and server. By default in most environments these messages will be written to a log file. You have control over whether a log file will be produced and where, write your own messages, and take control over the logging process. In an n-tier application the client and server logs contain different messages, since different activities are performed by each. Although the server log will record the activities (such as login and fetch) of the clients connected to it, its log is not a consolidation of all client logs.
The Debug log By default, DevForce will create a file of trace and debug messages in all environments except the Silverlight client and Azure*, as long as you specify the logFile in the logging element of the ideablade.configuration section: XML <logging logFile="DebugLog.xml"/> Writing to the file system is not supported in Silverlight and Azure, but see the topic on writing a custom logger for how to implement your own logging. To disable writing to the log file, set the logFile value to an empty string, or remove the logging element from the config. If you don't have an ideablade.configuration section in your config file, or an error prevented it from loading, DevForce will initialize a default configuration. That default will write a file named "DebugLog.xml" - to a log sub-folder for a web application, otherwise to the same folder as the executable. To change the location of the log file, just include the full path and file name wanted. For example, to write the log to a folder named "c:\AppLogs" with a name of "SalesAppLog.xml", set logFile="c:\AppLogs\SalesAppLog.xml". If the folder does not exist, or lacks write permissions, the application will run but a file will not be created. If the folder is a sub-folder of the application root folder, use a relative path name, such as "log\MyLog.xml". Note that a tilde in a relative path name is not supported here. The <logging> element in more detail Since we've looked at the <logging> element above, let's look at a few other properties you can use to control logging. XML <logging logFile="DebugLog.xml" archiveLogs="false" shouldLogSqlQueries="false" /> Set archiveLogs="true" to archive log files. By default this is off, but when on the previous log file will be renamed with a timestamp indicating the time the file was archived. Set shouldLogSqlQueries="true" to write the SQL for all entity queries to the log. This can be particularly helpful in debugging. We describe other settings in the discussion of real-time trace viewing. Generate custom messages The TraceFns and DebugFns classes (in IdeaBlade.Core) allow you to generate trace and debug messages from your code. Both TraceFns and DebugFns provide identical behavior, except that TraceFns calls will always execute, since in Visual Studio .NET projects the conditional TRACE flag is enabled by default for both release and debug builds. For example, you can use DebugFns.WriteLine to write diagnostic logging messages wanted only during testing of debug builds, while TraceFns.WriteLine can be used for the logging of messages you want in the deployed application. Use a custom logger Last modified on March 29, 2011 17:07 You can replace DevForce logging with your own custom logger, or multiple loggers, to take full control over logging in your application. The ITraceLogger is a simple interface DevForce will search for (via the ITraceLoggerProvider), and call the logger's Log method whenever a trace message should be logged. The Log method, although typed as receiving an Object, generally receives a TraceMessage, which you can inspect to see where and when the message was generated and by whom. The debug log is actually generated by an ITraceLogger implementation, the TraceFileXmlLogger. If you don't provide your own ITraceLogger implementation, then it's the TraceFileXmlLogger which is used. If you do provide your own logger, the TraceFileXmlLogger will not be used. In Silverlight and Azure, two environments where a debug log is not written, a custom ITraceLogger is a good way of capturing trace and diagnostic information. Here's a simple sample, which writes logging messages to the Console. Note that DevForce has a built-in ConsoleLogger, so if you do want to log to the Console you don't need a custom logger. C# using System; using IdeaBlade.Core;
namespace Samples {
public class SampleLoggerProvider : ITraceLoggerProvider {
public ITraceLogger GetLogger() { return new SampleConsoleLogger(); } }
// Sample logger - note that DevForce supplies a class named ConsoleLogger - this is shown here // as a sample only. public class SampleConsoleLogger : ITraceLogger {
/// <summary> /// Return true if your logger is not thread-safe. /// </summary> public bool IsSingleThreaded { get { return false; } }
/// <summary> /// Log the trace message - here we write to the Console. /// </summary> /// <param name="message"></param> public void Log(object message) { Console.WriteLine(message); } } } VB Imports System Imports IdeaBlade.Core
Namespace Samples
Public Class SampleLoggerProvider Implements ITraceLoggerProvider
Public Function GetLogger() As ITraceLogger Return New SampleConsoleLogger() End Function End Class
' Sample logger - note that DevForce supplies a class named ConsoleLogger - this is shown here ' as a sample only. Public Class SampleConsoleLogger Implements ITraceLogger
''' <summary> ''' Return true if your logger is not thread-safe. ''' </summary> Public ReadOnly Property IsSingleThreaded() As Boolean Get Return False End Get End Property
''' <summary> ''' Log the trace message - here we write to the Console. ''' </summary> ''' <param name="message"></param> Public Sub Log(ByVal message As Object) Console.WriteLine(message) End Sub End Class End Namespace
Real-time trace viewers Last modified on April 29, 2011 08:38 Contents Trace publishing Trace viewer in Silverlight Trace viewer utilities Using a trace viewer in your application The debug log is a historical record of an application's activities: it always starts when the application starts, and is continuously appended to. But DevForce also provides several "trace viewers", components which provide a real-time view of tracing as it occurs. A trace viewer can be started and stopped at any time, run as a standalone utility or as part of your application, and view tracing messages from different sources.
Before we launch into a discussion of the trace viewer utilities we need to take a slight detour and discuss trace "publishing" to help understand how a viewer works and what it can view. Trace publishing Trace and debug activity within a DevForce application uses the "publish-subscribe" metaphor: messages are always published, and any number of subscribers may listen in. This publishing is generally local to the application itself and a subscriber will listen only for its own activity. Except in Silverlight client applications, these messages can also be broadcast, or published, as part of a "trace publisher" service, to make the messages available to remote subscribers. (For those interested, the publisher is a WCF service.) By default, the console and Windows Service versions of the server (ServerConsole.exe and ServerService.exe) publish their messages. In a web application, the service can be started by calling the following, generally in the global.asax: C# IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable(); VB IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable() A client application can also publish its trace activity, using the same technique. As a service, the publisher must have a name and port. The default address in DevForce is net.tcp://localhost:9922/TracePublisher: where net.tcp is the protocol, 9922 is the port, and TracePublisher is the service name. Remember the logging element in the ideablade.configuration? Now we see where some of the "advanced" properties come into play: XML <logging logFile="DebugLog.xml" port="9922" serviceName="TracePublisher" /> Both port and serviceName can be modified to override the defaults. You can also override the defaults when calling MakeRemotable, passing a port number and service name. Why change the defaults? If multiple applications or services will be publishing trace messages on the same machine, you must change either the port or service name in order to provide a unique URL. Now back to the trace viewers. They will "subscribe" to a publisher to view its messages. That publisher is usually a service as described above, but it can also be the local publisher. How does the ITraceLogger compare with a "subscriber"? The ITraceLogger will only receive messages from the application it's a part of, it will never automatically receive messages from another trace publisher. The ITraceLogger also will receive messages throughout the life of the application, it can't subscribe and unsubscribe. Trace viewer in Silverlight A debug log is not generated for a Silverlight client application since writing to the file system is not supported for browser applications, but tracing and debugging messages are still generated. You can listen for these messages with a custom ITraceLogger, and you can also use a simple trace viewer user control sample we provide. The sample viewer, when "dropped" into an existing window, provides a simple real-time display of tracing and debugging messages generated on the client. Note that messages from the server will not be shown by this viewer. Trace viewer utilities To use a trace viewer as a standalone executable subscribed to a remote publisher, launch the executable, either WinTraceViewer.exe or WPFTraceViewer.exe. As you might guess from the names, these are WinForms and WPF implementations of a viewer with the same basic functionality. Both utilities can be found in the Tools sub-folder of the DevForce installation.
By default the trace viewer will attempt to subscribe to the well-known address mentioned above: net.tcp://localhost:9922/TracePublisher. You can, however, subscribe to (and unsubscribe from) any publisher. The WPFTraceViewer also accepts a command line argument for the URL of the publisher, for example: wpftraceviewer.exe "net.tcp://localhost:9001/mypublisher" Using a trace viewer in your application You can also use a trace viewer directly in your Windows application, with the viewer opened as a new window. To do so, add a reference to either WPFTraceViewer.exe or WinTtraceViewer.exe (both available in the Tools folder of the DevForce installation) to your UI project. For example, right-click the references node in your desired UI project, and select Add Reference. On the Add Reference dialog, select the Browse tab, then browse to the file and click OK. To launch the WPF viewer: C# IdeaBlade.DevTools.WPFTraceViewer.WPFTraceViewer tv = new IdeaBlade.DevTools.WPFTraceViewer.WPFTraceViewer(); tv.Show(); VB Dim tv As New IdeaBlade.DevTools.WPFTraceViewer.WPFTraceViewer() tv.Show() ... or WinForms: C# IdeaBlade.DevTools.TraceViewer.TraceViewerForm tv = new IdeaBlade.DevTools.TraceViewer.TraceViewerForm(); tv.Show(); VB Dim tv As New IdeaBlade.DevTools.TraceViewer.TraceViewerForm() tv.Show() Both viewers have constuctor overloads which allow the trace publisher URL to be specified, but by default these viewers will subscribe to the local publisher in the application. To see this in action see WPF Trace Viewer code sample. Debug Last modified on March 23, 2011 15:55 There are a number of resources available to help debug your application and diagnose the cause of problems when they occur. Use DebugFns.WriteLine to write your own diagnostic and tracing messages to the DevForce log. In Silverlight, implement a custom logger or use the sample trace viewer to capture trace and debug messages. Check the debug and trace messages on both client and server! The historical record of your application processing is normally invaluable to IdeaBlade Support, and can offer many helpful clues in diagnosing problems. We can't emphasize this strongly enough - understanding the trace messages DevForce generates can help you quickly diagnose problems. If you're curious about the SQL generated for a query (and if you're not you should be, since the results may surprise you), the shouldLogSqlQueries flag on the <logging> element controls whether generated SQL is written to the log: XML <logging logFile="DebugLog.xml" shouldLogSqlQueries="true" /> If you're using SQL Server, learn to use SQL Profiler to capture and review all server activity. EF Prof, an Entity Framework profiler, is an excellent tool for debugging all database activity from your application. If you're using VS 2010 Ultimate, take a spin through Intellitrace. If you've deployed to Azure and run into problems, both a custom logger and Intellitrace can be helpful. Check the Event Viewer. The entityserver writes a few startup messages to the application Event Log, with a source of "DevForce Object Server". Any problems with initializing the default debug log will be written to the Event Log, as well as startup messages from the console and Windows Service servers (ServerConsole.exe and ServerService.exe). These messages indicate how the services were configured, what the endpoint addresses are, and any errors encountered. For communication-related problems in n-tier applications, or just to take a peek on what's being sent between the client and server, Fiddler is very helpful. For really difficult problems with n-tier communication problems, the Service Trace Viewer comes in handy. You can find the utility in the Windows SDK Tools (the executable name is SvcTraceViewer.exe).
You must first enable diagnostics to use this utility, for example: XML <configuration> <system.diagnostics> <sources> <source name="System.ServiceModel" switchValue="Information, ActivityTracing"> <listeners> <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\temp\ServerConsoleTrace.svclog"/> </listeners> </source> </sources> </system.diagnostics> </configuration> Enable ASP.NET trace information for an IIS-hosted EntityServer to see information on client- server requests. Understand logged info Last modified on March 28, 2011 16:32 Contents Discovery Part 1 License Housekeeping Communications Discovery Part 2 Login Fetch and save processing Exceptions The messages in the debug log may at first look obscure to you, but once you understand what's being written, when, and why, they can often help in diagnosing problems. Understanding "normal" activity is also important, so you know what to look for when something does go wrong.
Most interesting tracing messages from DevForce are generated at start up. At initialization the IdeaBladeConfig.Instance is loaded, "probing" occurs, and service communication is initiated. It's during this time that DevForce does most of its logging about how the application is configured, and here where you'll find the most useful diagnostic information. Once processing is underway, DevForce performs little logging of its own actions. Custom trace and debug messages generated from your application will usually help in understanding your standard processing and any errors or unexpected situations. In reading through a log you'll notice that messages aren't always sequential, particularly in the server log. This is because logged actions can occur on different threads, often concurrently, and is usually not a cause for concern. Discovery Part 1 Let's look at discovery, or probing, first.
DevForce uses MEF (the Managed Extensibility Framework) to provide extensibility. One of the first actions performed is to find the default and custom implementations of various interfaces and base classes. The default implementations are found in the DevForce assemblies - for example, if you haven't implemented a custom IEntityLoginManager, we'll fall back to using one of our defaults. The first message shows the time probing started and a helpful reminder: if the timestamps showing when probing starts and completes indicate the process is taking too long, see the Extend topic for information on how to control this. The second message tells us that one of our assemblies was added to the "parts" catalog. In this application several custom interface implementations are defined in the assembly listed, so it's a good bet that our implementations have been found and will be used when needed. If you've implemented any custom implementations and don't see one or more messages about the assemblies being added to the parts catalog, then stop and figure out why; maybe the assemblies weren't found. If an assembly couldn't be loaded you'll also find that message here: an assembly load failure is usually due to missing references. Finally we see when probing completed, and all the assemblies DevForce found. It's worth examining this list for two reasons: 1) if the list contains a lot of assemblies it's likely the whole probing process is taking too long and also likely that DevForce really does not need to know about every assembly; and 2) if the list does not contain an assembly that it should then the application will likely experience a failure later. DevForce needs to find any assembly containing your model(s) and your custom implementations and known types, but other assemblies are usually superfluous and will slow down the initialization process. To fix this, see the Extend topic. License Next, the all-important license message:
Why is this important? If the license information is not found the application won't run at all. If the license information is incorrect, then functionality may be reduced. It's common when upgrading your license - say from Express to Universal - that you've neglected to re- generate the code for your entity model. At run time DevForce uses the license found in the entity model to determine the functionality provided. If you see in the log that the wrong license was found, first re-generate your model code and try again. (By "re-generate" we mean only to either open the EDM Designer and save, or select "run custom tool" for the .tt file - it's the designer code file which must be re-generated.) You'll also see a message (not shown here) about whether a "session-agnostic load balancing" license was found. If you have a Data Center license then load balancing support should be enabled. Housekeeping Now for some housekeeping. You'll see somewhat different messages in different environments, here we show the EntityServer (hosted in the ASP.NET development server) and Silverlight. Entity Server -
Silverlight (note that a trace viewer was used to capture these messages) -
The messages will show how the IdeaBladeConfig was loaded. It's generally loaded from an ideablade.configuration section found in a .config file (but you can also initialize it in code). For the EntityServer we see that the section was found in the web.config file, while in Silverlight we see that an app.config was not found and the default configuration will be used. Since an app.config is generally not needed in a Silverlight application this is standard. For any environment, if an error message is logged that the configuration was found but failed to load, ensure that the xml of the .config is correct. The DevForce version is logged next. Simple, you think. But we've found that it's very easy after a DevForce upgrade to re-compile and/or re-deploy the wrong assemblies, or have the client assemblies at one version and the server assemblies at another. The DevForce version must be the version you expect, and the version must be the same on both client and server. Communications Next, the service/proxy start up information is logged. This information will be different on client and server, since on the server the WCF services are started, while on the client proxies to these services are created. Remember that a single EntityService service is started, and then an EntityServer service for each data source extension and composition context. If you're not using either data source extensions or composition contexts, then you'll have only a single EntityServer. Note that there will be some intervening messages not shown here.
What we see here are the services starting and the endpoint addresses on which the services listen. In this case we see two endpoints for each service. This is because DevForce will automatically add an endpoint based on the SupportedClientApplicationType setting. This setting defaults to "UseLicense", which means that DevForce will create endpoints for whatever the license allows. In this sample we're using a Universal license, and endpoints were created for both Silverlight and non-Silverlight clients. Why does Silverlight need its own endpoints? The communication setup and error handling are a bit different. XML <serverSettings supportedClientApplicationType="UseLicense" /> In a deployed application you should enable only the endpoints wanted. If your EntityServer will support only Silverlight clients, there's no reason to add non-Silverlight endpoints too. Here's the communication start up for a Silverlight client:
We see that proxies were created for the Silverlight endpoint addresses we saw above. Communication failures are the most common type of initialization problem. Life would be easy if the cause of every communication failure was neatly written in the logs, but this is often not the case. Error messages from caught exceptions will be logged, but if a service cannot be started this can often not be logged, so using other debugging techniques may be needed. The failure of a service to start is usually caused by an IIS compilation failure or a configuration problem, and is not logged because the application and DevForce never start. Also, if you see a mismatch between the service endpoint addresses and the addresses the client is trying to use, that's always a good indication of why communications are failing. Discovery Part 2 Scattered throughout the log file we'll also see messages from the CompositionHost, issued when an interface or base class extensibility point is encountered. Generally these messages are generated the first time something is probed for, but in some cases you'll see a message for every attempt. For any of your custom implementations you need to check that it was found and used. When not found DevForce will generally use the default implementation, and your application may not work as wanted. A message such as the following:
... indicates that DevForce is looking for a custom implementation but didn't find one, while the message below:
... indicates that DevForce is looking for any custom or default implementation and has found the default. Below we see a message when our custom implementation was found:
It's good practice to check the logs, on both client and server, to ensure that all your custom implementations have been found and used. Remember that different extensibility types may be probed for on client and server: for example the IEntityLoginManager should be on the server only, while a ServiceProxyEvents implementation would be on the client only. Login Login information won't be recorded until the first user tries to log in. At that time, DevForce will search for an IEntityLoginManager, and record other information about the authentication environment.
Above we see that ASP.NET Windows security has been enabled, guest access disabled, and the standard DevForce ASP.NET login manager is in use. After the first login, further information on client logins will not be recorded by DevForce. A custom login implementation may want to record when users log in or out. Fetch and save processing Some information will be logged for every query which will go to the data source (note you will not see these messages in the client log). This isn't the SQL for the query, but a simple string representation of the EntityQuery.
The first time an entity is encountered for a specific data source, this information will be logged too. The DataSourceKey must be resolved - through either the connectionStrings, EdmKeys or code - and the EF metadata found. You'll see either a message that the key was resolved, or an error concerning the connection string or metadata. Generally, little information is logged for save processing unless an error occurs. You'll see probing messages for the EntityServerSaveInterceptor and several other types, but no specific information is recorded about each save. Exceptions On the server, most exceptions are caught and written to the log in their entirety, so checking the server log for exceptions is usually a good idea. All exceptions occurring during a save will be logged in the server's log: these exceptions often indicate "normal" processing, for example concurrency errors. The default EntityServerErrorInterceptor will also log all exception text for any unhandled error to the server log.
Understand the EntityServerException Last modified on March 30, 2011 10:59 Contents Details Catching the exception When requests to the EntityServer fail (including query and save requests), the server-side exception is wrapped in an EntityServerException and sent back to the client. Understanding the EntityServerException can help in debugging application problems. You may be able to catch the exception and recover - an option that begins with listening to the EntityManager.EntityServerError event.
If a request to the EntityServer fails the exception returned to the client application will be wrapped in an EntityServerException. Your application might receive the base EntityServerException, or one of the custom sub-types: EntityManagerSaveException for save failures. EntityServerConnectionException for a subset of EntityServer connection failures. LoginException for login failures. PersistenceSecurityException for a subset of unauthorized access attempts.
Details
While the text of the exception message isn't always helpful, since exceptions can bubble up through many layers, in not only DevForce but in .NET and other assemblies, you'll often have to look at other details in the exception to understand the problem. All EntityServerExceptions will have this information: FailureType will indicate the general reason for the failure: for example Data will indicate a query or save failed at the database, while Connection will indicate a communications failure. There are a handful of other reasons too. OperationType indicates the operation being performed at the time of failure, for example Query or Save, among others. For failures occurring on the EntityServer in an n-tier application the InnerException will often be empty because that information is not serialized to the client. Instead, several other properties will contain this information: RemoteExceptionDetails - The ToString() representation of the original exception. RemoteExceptionName - The exception class name. RemoteSource - The source of the original exception. RemoteStackTrace - The stack trace of the original exception. If the InnerException is present, drilling down into it and all nested inner exceptions can help determine the original cause of the failure. Catching the exception If you've made a synchronous call the EntityServerException will be thrown, and your try/catch logic should catch the error. In an asynchronous call, use the Error property of the operation or event arguments to diagnose the failure. Remember when making an asynchronous call that if your application should continue running after the failure you should call MarkErrorAsHandled(). The EntityManager also provides a central error handling facility for EntityServerExceptions. The EntityServerError event will be raised as a "first chance" handler for any exceptions before they are thrown. In your event handler you can mark the exception as handled so that it will not be re-thrown. The handled exception will be returned to an asynchronous operation, however. Understand and troubleshoot timeouts Last modified on April 27, 2011 12:37 Contents Database / transaction timeouts o Query o Saves Communication timeouts IIS There are several timeouts which can affect your application. Learning to understand and troubleshoot timeouts will help to quickly diagnose and fix problems.
If you've received a "timeout" exception, the first question to ask is "Where is it coming from?" In a n-tier application the timeout might come from the database server, the communications infrastructure (WCF), or even IIS. In a 2-tier application not hosted in ASP.NET, the timeout exception will originate from the database. Any exception you receive will be an EntityServerException or one of its subtypes. Database / transaction timeouts You might receive a database-related timeout during either a query or save. Query
You receive an EntityServerException with a FailureType of Data and OperationType of Query. The message itself may be less than helpful, it might say something like "An error occurred while executing the command definition...", or maybe it will say "Timeout expired". In a 2-tier application you can drill into the inner exceptions to see the underlying database exception and message. In SQL Server that will be a System.Data.SqlClient.SqlException with a message which begins with "Timeout expired." In an n-tier application, you'll need to check the RemoteExceptionName and RemoteExceptionDetails to find that information. To fix this problem, modify the query's CommandTimeout. Every query contains a CommandTimeout property which you can set to override the provider's default query timeout value. With SQL Server, that default timeout is 30 seconds. If the CommandTimeout is sufficient but the transaction timeout is not, you'll get an EntityServerException with a message stating either "The transaction has aborted" or "Transaction timeout". In some cases the error message will state "The transaction associated with the current connection has completed but has not been disposed." The FailureType is Other in all cases. By default, queries will use TransactionSettings with a UseTransactionScope option set to true, which indicates that the query should be wrapped in a TransactionScope. When a query is wrapped in a TransactionScope then the Timeout value on the TransactionSettings determines the allowed time for the overall transaction. The TransactionSettings can be passed with the QueryStrategy or set on the EntityManager.DefaultQueryStrategy. The default timeout value is 1 minute. If your query does not require a TransactionScope you can set UseTransactionScope to false. Saves
You receive an EntityManagerSaveException with a FailureType of Data and an OperationType of Save. You'll need to drill into the details of the exception to see the underlying System.TimeoutException with the "Transaction Timeout" message. You cannot set the command timeout for individual inserts, updates and deletes performed as part of the save processing. Here only the TransactionSettings.Timeout can be used to alter the processing timeout. You can set the desired timeout on the SaveOptions passed from the client, or on the EntityServer with the TransactionSettings.Default singleton. The default timeout is 1 minute. You receive an EntityManagerSaveException with a FailureType of Other and an OperationType of Save. The error message states "The transaction associated with the current connection has completed but has not been disposed." This will occur when the outer TransactionScope times out before the inner database transaction completes. To resolve the problem set a larger timeout value on the SaveOptions passed from the client, or on the EntityServer with the TransactionSettings.Default singleton. The default timeout is 1 minute. Communication timeouts Most communication-related timeout error messages will state something along the lines of "The HTTP request to 'http://yourserver/EntityServer.svc' has exceeded the allotted timeout." If you drill into the exception details you'll usually see a System.TimeoutException wrapping a System.Net.WebException. You might get a timeout for any type of request to the EntityServer, and they can occur regardless of whether the request was synchronous or asynchronous. The default SendTimeout on the client is 1 minute, and it's this timeout that is most often the cause of communication timeouts. You can override the default value in two ways - via either configuration or code. See the advanced configuration topic for more information. There are additional timeout settings available on both client and server, but they don't often need to be adjusted from their default values. If you are saving large amounts of data then you might find that in addition to the client's SendTimeout you also need to adjust the server's ReceiveTimeout. In your 2-tier application you obviously don't need to be concerned with communications exceptions. IIS Even when your WCF timeouts seem sufficient, you might receive an exception stating that "The operation has timed out.". The problem might be in another setting applicable only when hosting the EntityServer under IIS, the executionTimeout. The default value is 110 seconds, and can be modified via the web.config: XML <system.web> <httpRuntime executionTimeout="110"/> </system.web>
Configure and deploy Deploy n-tier Last modified on March 23, 2011 23:50 In the following sections we'll explore the many options and configuration settings available to you as you configure and deploy an n-tier application. In an n-tier DevForce application, the client tier communicates with an EntityServer on a separate application server tier, and the application server in turn communicates with the data tier. The client tier The typical DevForce n-tier client is a smart .NET UI such as a Silverlight, Windows Presentation Foundation (WPF), or Windows Forms application, but it might also be a console application, a Windows Service, or an ASP.NET application. We discuss configuration and deployment of Silverlight and other application types separately. The typical DevForce n-tier client is a smart .NET UI such as a Silverlight, Windows Presentation Foundation (WPF), or Windows Forms application, but it might also be a console application, a Windows Service, or an ASP.NET application. In an n-tier DevForce application, the client tier communicates with an EntityServer on a separate application server tier, and the application server in turn communicates with the data tier. We'll discuss configuration and deployment of Silverlight and other application types next. Deploy Silverlight client Last modified on March 23, 2011 23:52 Contents Assemblies Packaging Configuration Security Troubleshooting o FIPS Compliance Deployment of your Silverlight application will generally be performed as part of the deployment of the EntityServer to a web site. We have additional information on configuring the web site here.
Assemblies The following assemblies are always required: IdeaBlade.Core.SL.dll IdeaBlade.EntityModel.SL.dll IdeaBlade.Linq.SL.dll IdeaBlade.Validation.SL.dll You'll of course also need all of your own client-side assemblies and other files needed by your application. If you've implemented any custom extensions of DevForce components, remember that some will be pertinent to only the client or only the server. In these cases, particularly for server-only components, it's best to deploy these assemblies to only the appropriate tier. Packaging Generally, Visual Studio has taken care of the packaging of your Silverlight application into one or more deployment packages, but there are a few additional things to consider. Your Silverlight application will generally have a main XAP, and possibly additional secondary deployment packages to be downloaded as needed. The DevForce Silverlight assemblies support Application Library Caching to help reduce XAP size. You can enable your application to run out-of-browser and automatically update when the application changes. DevForce supports OOB Silverlight applications, and has additional offline support. Since the client browser will usually cache your XAP files, you should ensure that updates to your application will be seen by your users. There are several ways of ensuring this, but the easiest might be to append information to the URL for the XAP file to indicate your current application version. For example: XML <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> <param name="source" value="ClientBin/MySilverlightApp.xap?version=1.0.0" /> ..... </object> You'll need to ensure that the client browser has the appropriate version of Silverlight installed, and you may want to customize the Silverlight install experience for your users. On the server, you'll deploy any XAPs (and ZIPs for ALC-enabled assemblies) to the ClientBin folder of your web site. You'll also need to ensure that the XAP mime type is registered in IIS. See http://learn.iis.net/page.aspx/262/silverlight for more information on registering the mime type for different IIS versions. Configuration By default DevForce will assume that the EntityServer is located in the same location from which the XAP was downloaded. For example, if the XAP for your application was downloaded from http://myhost/myapp/default.aspx, DevForce will default the service URL to http://myhost/myapp/EntityService.svc. If your EntityServer is located at this address, then you won't need an app.config in your Silverlight application. If you're using SSL and the XAP is downloaded using https, then the default EntityServer address will also use https. If your EntityServer is at a different location, or you need to customize the communication defaults, then you will need to override the DevForce defaults. If only the location needs to be changed you can include an app.config file in your application with the appropriate <objectServer> information. Instead of including an app.config, you can also specify <objectServer> information programmatically by working directly with the IdeaBladeConfig.Instance when the application starts. If you need to customize communications (such as timeouts) you can add a ServiceReferences.ClientConfig file to your application to specify the <system.serviceModel> information required, or implement a custom ServiceProxyEvents to customize configuration at run time. (Note that you cannot define the GZip binding extension in the ClientConfig; DevForce uses this in the default configuraiton to compress all requests to and responses from the server. If you use a ClientConfig file you will need to add the GZip binding element programmatically. See this sample for more information.) For more information on the default configuration and how to customize it, see here. If your Silverlight application is not at the same location as your EntityServer you will also need to deploy a clientaccesspolicy.xml file to the server hosting the EntityServer to enable cross domain access. Sample Silverlight app.config, ServiceReferences.ClientConfig and policy files are provided in the deployment snippets. Security Remember that a Silverlight application is vulnerable to malicious users. Its code can be disassembled, its Isolated Storage peered at, and its communications with the application server tampered with. See the Security topic for more information on steps you can take to secure your application. Troubleshooting See the topic on n-tier troubleshooting for more information. 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> <compilation debug="false"> </system.web> If you use an .html page instead of an .aspx page to host the Silverlight XAP control, you will need to delete the following: XML <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
The application server tier The Entityserver is responsible for accessing data sources and helping to secure your application. The EntityManager in the client application makes requests to the EntityServer whenever querying (when the query can't be satisfied from cache) or saving entities, and to call custom service methods. The EntityServer is implemented as several Windows Communication Foundation (WCF) services and can be deployed in different ways. The data tier We don't really have much to say about your database(s) and leave its configuration to you and your DBA. You should consider security, however, both in terms of connection information stored in connection strings, access accounts, and transmission security. DevForce imposes no special requirements on the data tier. Deploy client application Last modified on June 30, 2011 17:31 Contents Files and assemblies The config file o Logging o ObjectServer Packaging for deployment Security A DevForce n-tier client is often a Silverlight, Windows Presentation Foundation (WPF), or Windows Forms application, but it might also be a console application, a Windows Service, or an web application. Although deployment differs for these different application types, the requirements of each are generally the same.
Files and assemblies The following assemblies are always required: IdeaBlade.Core.dll IdeaBlade.EntityModel.dll IdeaBlade.Linq.dll IdeaBlade.Validation.dll You'll usually need your .config file too: app.config.exe (or web.config) You'll of course also need all of your own client-side assemblies and other files needed by your application. If you've implemented any custom extensions of DevForce components, remember that some will be pertinent to only the client or only the server. In these cases, particularly for server-only components, it's best to deploy these assemblies to only the appropriate tier. You may place the DevForce assemblies in the GAC, although there's generally no compelling reason to do so, unless you have a large number of DevForce applications installed and they are all using the same DevForce version. You do not need to, and in most cases should not, install DevForce to the target machine. Runtime license information is found in your model assembly and not the registry. The config file You do not need the <connectionStrings> (and <edmKeys>) in your client application (unless you are also directly accessing local resources). Having database connection information present represents a security vulnerability, easily fixed by removing this information. You'll generally use the <ideablade.configuration> section to provide connection information to the EntityServer and logging information, although neither is strictly necessary. Logging You can use the <logging> element to set the file name and location of the debug log, or to turn logging off altogether. You also might want to archive log files. Generally other logging attributes are not needed, or used only for debugging purposes. A typical config file might contain the following logging information: XML <logging logFile="log\DebugLog.xml" archiveLogs="true" /> ... to place the file in a sub-folder named log and archive files automatically. Also see the Log topic for more information. ObjectServer You'll usually use the <objectServer> element to specify the location of the remote EntityServer and to enable remote communications. Note that in Silverlight, the <objectServer> element is not required, as DevForce will use the URL from which the XAP was downloaded to infer the server address. We say "usually" because you can also specify this information programmatically, by working directly with the IdeaBladeConfig.Instance when the application starts. Other options are to use the <system.serviceModel> element to configure communications, or to implement a custom ServiceProxyEvents to customize configuration at run time. For more information on how DevForce determines the defaults and how to customize them, see the advanced configuration topic. A typical config file might the contain the following to communicate with an IIS-hosted EntityServer: XML <objectServer remoteBaseURL="http://yourserver" serverPort="80" serviceName="yourapp/EntityService.svc" > <clientSettings isDistributed="true" /> </objectServer> If your server is using SSL, then it might look something like this instead: XML <objectServer remoteBaseURL="https://yourserver" serverPort="443" serviceName="yourapp/EntityService.svc" > <clientSettings isDistributed="true" /> </objectServer> ... where we've changed the protocol and port information. If your EntityServer is hosted by either the DevForce-supplied ServerService or ServerConsole hosts, then it might look something like this instead: XML <objectServer remoteBaseURL="http://yourserver" serverPort="9009" serviceName="EntityService" > <clientSettings isDistributed="true" /> </objectServer> Note the differences from when your server is hosted under IIS: the serviceName does not contain the web application name or .svc extension, and the port is usually not port 80. The port can be any free port that you've configured your server to listen on - we often use port 9009 in our samples (as we did here) but there are no DevForce requirements for this port number. Regardless of the host, your client configuration should match the server's configuration. If the EntityServer is using https, then the client must too. If the server is listening on port 80, then the client must use that port. The <serverSettings> are not pertinent to the client application in an n-tier deployment. Packaging for deployment The computers on which you deploy the application likely have the .NET Client Profile or full .NET Framework 4.0 already installed or you will install it as part of your deployment. Note that an n-tier client application, unlike a two-tier client application, can be deployed with the .NET Client Profile without additional assemblies. This is because the IdeaBlade.EntityModel.Server assembly is not deployed with the client application. Generally client applications are installed via an installer or a ClickOnce deployment. Security Remember that a client application can be compromised. Its code can be disassembled, its configuration, log and other files peered at, and its communications with the application server tampered with. See the Security topic for more information on steps you can take to secure your application. Connect to multiple application servers Last modified on April 22, 2011 10:17 Contents Using ServiceKeys Difference from data source extension and composition context The default key On the server Using a system.serviceModel An n-tier client application may connect to more than one application server.
In most n-tier applications, the client application needs to communicate with only a single "host" of the EntityServer services. By "host" we mean the machine name, port, protocol and application name forming the URL information commonly found in the <objectServer> element in your configuration file. We can consider each "host" to be a separate application server. Communicating with a single application server, to login, and perform queries and saves, is sufficient for many applications. But DevForce also allows a client application to work with multiple application servers, both at the same time and as needed based on application requirements. For example, your client application may need to work with HR services at site A while also working with payroll services at site B. Or maybe your application will sometimes connect to a local application server but at other times needs to work with the server at headquarters. (In all cases the application server is a DevForce EntityServer, not custom or third party web services.) When your application needs to work with multiple application servers, you do so through something called a serviceKey. Using ServiceKeys ServiceKeys provide address information for the application servers your client application may work with. These keys are defined in your configuration file and allow you to specify a name and address for each application server. The serviceKey provides additional flexibility in defining the application server over the fixed information in the <objectServer> element. You choose the serviceKey to use, and thus the application server, when you construct an EntityManager. C# var entityManager = new NorthwindIBEntityManager(new EntityManagerContext(serviceKey: "foo")); VB Dim entityManager = New NorthwindIBEntityManager(New EntityManagerContext(serviceKey := "foo")) That serviceKey name, here we've used the silly name of "foo", tells DevForce to look for a serviceKey with this name in the configuration file to obtain the address of the application server. (You can still use the <system.serviceModel> section to define the service endpoints in advanced configurations, we describe that below.) Here's the configuration information for the "foo" key: XML <objectServer remoteBaseURL="http://www.contoso.com" serviceName="BigApp\EntityService.svc" serverPort="80" > <serviceKeys> <serviceKey name="foo" remoteBaseURL="http://foo.contoso.com" serviceName="SmallApp\EntityService.svc" serverPort="8080" /> </serviceKeys> </objectServer>
An EntityManager constructed with this serviceKey will query and save to the application server located at the http://foo.contoso.com address. Other EntityManagers in the application can continue to communicate with the default application server at http://www.contoso.com. Difference from data source extension and composition context In other topics we've discussed the use of multiple EntityServers. An EntityServer is created to match the specifics of the requesting EntityManager. If you are using either a data source extension or custom composition context when you create your EntityManager, then it will communicate with an EntityServer having those same characteristics. How does this differ from service keys? When using either a data source extension or custom composition context your application will still communicate with the same application server. The address of the EntityService will be the same, and that EntityService will determine the specific EntityServer to be used. With a serviceKey, an entirely different application server, and thus EntityService, will be used. That application server might be on a different machine altogether, or use a different port or protocol. You can still use data source extensions and/or custom composition contexts when using a serviceKey. The default key When constructing an EntityManager you can provide a serviceKey in the constructor arguments. By default, if the serviceKey is not provided then DevForce will use the default key to determine the application server it will use. The determination of this default is worth noting. Generally the remoteBaseURL and other <objectServer> attributes will be used to determine the default key. (In Silverlight applications this information is set for you based on the address the XAP was loaded from.) If this information isn't defined then the serviceKeys are searched. If a serviceKey named "default" is found (the search is case-insensitive) then that serviceKey is used as the default, otherwise the first serviceKey defined is used as the default. The following are different ways to specify your keys. In each, the default key found by DevForce will be the same - the one resolving to an address of http://www.contoso.com/Samples/EntityService.svc. XML <objectServer remoteBaseURL="http://www.contoso.com" serviceName="Samples\EntityService.svc" serverPort="80" > <serviceKeys> <serviceKey name="Backup" remoteBaseURL="http://test.contoso.com" serviceName="OldSamples\EntityService.svc" serverPort="8080" /> </serviceKeys> </objectServer>
On the server The purpose of ServiceKeys is to allow you to specify the various application servers your client application may communicate with. But, since it's often easier to copy configuration information between client and server config files, the server can also use the serviceKeys in some configurations. If an EntityServer is deployed as either a console application or Windows service then DevForce must determine a "base address" for its services. This address is determined based on the default key logic described above. Using a system.serviceModel If you've found you need the full control the <system.serviceModel> configuration section offers, you can still use it to define endpoints when using serviceKeys. DevForce will look for endpoint names with the {ServiceKey}_{ServiceName} format. In other words, if your serviceKey name is "foo", then DevForce will look for endpoints named "foo_EntityService" and "foo_EntityServer". Note that the bindings do not need to be the same across serviceKeys, so for instance one application server might use https while another uses http. XML <system.serviceModel> <client>
<!-- Endpoints for the "default" server --> <endpoint name="EntityService" address="http://localhost:9009/EntityService.svc/sl" binding="customBinding" bindingConfiguration="CustomBinding" contract="IdeaBlade.EntityModel.IEntityServiceContractAsync" />
Server tier Last modified on April 20, 2011 08:52 In an n-tier DevForce application, the client tier communicates with an EntityServer on a separate application server tier, and the application server in turn communicates with the data tier. The EntityServer is responsible for accessing data sources and helping to secure your application. The EntityManager in the client application makes requests to the EntityServer whenever querying (when the query can't be satisfied from cache) or saving entities, and to call custom service methods. The EntityServer is implemented as several Windows Communication Foundation (WCF) services and can be hosted in different ways, as shown below. Deployment Description Console Application This is perhaps the simplest deployment, and best suited for development purposes. The EntityServer services are hosted by the ServerConsole.exe application. The services will shutdown when you terminate the console application. Windows Service The EntityServer services may be hosted by a Windows service, installed by the ServerService.exe application. Once installed, the services can start when the machine boots, and can be started and stopped via the Windows Services management console. Internet Information Services (IIS) The most robust deployment of an EntityServer is via IIS. IIS has built-in support for WCF services, and provides memory management and fault tolerance capabilities. With an IIS deployment, you'll configure a web site with the appropriate configuration, and the EntityServer will start whenever a client application connects. The EntityServer might stop and start with more frequency, as IIS manages the process. You'll use the IIS Manager to configure and manage the deployment. Windows Azure See our website for more information and a sample deployment. We'll discuss the details of each deployment in the following sections. Service or Server? You've probably seen the name EntityService used throughout these pages. You've seen it in the <objectServer> element in your config file. You'll see it when you customize WCF configuration with a <serviceModel> definition. Why are we using this name, yet talking about the EntityServer here? (And for that matter, why did we label that element "objectServer"? Well, that's a story for another day.) There are several components making up the server. The EntityService is a WCF service and functions as little more than a gateway or simple router: its primary purpose is to inform a requesting client which EntityServer it will be working with, and to either start that EntityServer service or ensure it's already running. It's the EntityServer which is responsible for all persistence and security activities. You might have multiple EntityServers too, as one is created to match the specifics of the requesting EntityManager. If you are using either a data source extension or custom composition context when you create your EntityManager, then it will communicate with an EntityServer having those same characteristics. In default configurations DevForce will take care of the details, but this is something to be aware of in more advanced configurations, where you might directly configure the services via either configuration or code. Here's a sample of the services you might see when using data source extensions of "A", "B" and "C".
Deploy to IIS
The most typical, and most robust, deployment of the EntityServer is with Internet Information Services (IIS).
Hosting the EntityServer WCF services in IIS has several benefits: IIS provides process activation, health management, and recycling capabilities to increase the reliability of hosted applications. Services hosted in IIS use a dynamic compilation model, which simplifies development and deployment. Services are deployed and managed like any other type of IIS application. When the EntityServer is running in ASP.NET Compatibility Mode it can participate fully in the ASP.NET request lifecycle and use ASP.NET Security features. IIS Setup The EntityServer can be hosted on any version of IIS from 5.1 forward. You'll generally use the IIS Manager (available from Control Panel | Administrative Tools to manage your application. Setup follows the same steps you would take for any IIS application. The only DevForce- specific requirements are: .NET Framework 4.0 On some older operating systems or versions of IIS you may need to ensure that the WCF HTTP activation component is correctly installed and registered. See troubleshooting for more information. It's usually easiest to use the Visual Studio Package/Publish features to create a deployment package and deploy your web application to an IIS server supporting web deployment. We have a walk through with more information and resources. If you want to do a manual setup, here are the steps (shown in Internet Information Services Manager for IIS 7.5): 1. On the web server, create a physical directory for your application under the web site root folder (typically c:\inetpub\wwwroot). 2. Create bin and log folders under the above directory. Note that the log folder is optional; see below for more information. 3. Create a new IIS application or reuse an existing application. Be sure to use an application pool which supports .NET Framework 4 and the integrated pipeline mode.
To add a new application, right click the Default Web Site and choose Add Application. Here we're creating the "BadGolf" application using the DefaultAppPool.
4. The log folder requires special setup as you'll need to both grant appropriate write permissions to create the DebugLog.xml, but also secure the file and folder from unauthorized access by removing read/browsing permissions. You can disable generation of the DebugLog altogether, but the diagnostics it produces are generally helpful, especially in a new deployment. 1. The application runs under the identity of the application pool, which will generally be DefaultAppPool unless you've created a custom application pool. You'll need to grant this account write permissions to the log folder. In IIS Manager, browse to the folder, right click and select Edit Permissions and the Security tab, and then edit the permissions for the account. 2. If the folder is located under the web application folder the DebugLog.xml contents will be browsable, so you must disable unauthorized access. In IIS Manager, browse to and select the applications log folder. Click the Authentication icon under the IIS section, then select Anonymous Authentication and right click to disable. This will prevent non-authorized persons from examining the applications debug log. Silveright considerations If your IIS application will also host the Silverlight application you need to take a few additional steps. 1. Create a ClientBin folder under the application folder. Copy all XAP and ZIP files used by your application into this folder. 2. Copy your *.aspx, *.html and Silverlight.js files to the application folder. 3. Ensure that the XAP mime type is registered in IIS. See here for more information on registering the mime type for different IIS versions. Additional considerations To use SSL to encrypt communications you'll need to obtain a certificate and configure the https binding for your site. Here's more information. The EntityServer can also use the net.tcp protocol instead of either http or https. You'll need to setup the Windows Process Activation Service to bind a non-HTTP port to your site. Here's more information. If you're deploying to an Internet Service Provider you should know that the EntityServer requires "full trust". This trust level is not always supported for shared hosting providers.
If using SQL Server Express databases (such as the aspnetdb database used with ASP.NET security), the advice from Microsoft is: "When deploying applications into production on any version of IIS, SQL Server Express user instances should not be used." The reason for this is that SQL Server Express versions 2005 and 2008 both run under the NETWORK SERVICE account. You will need to change the account identity of the application pool to NETWORK SERVICE in order to use your express database, and this is not recommended. Alternately, you can modify the connection string to use SQL authentication. (See Problems with SQL Server Express user instancing and ASP.net Web Application Projects for more information.) See the samples for additional steps needed to use Windows authentication. If deploying to production, make sure to turn off any diagnostics, WCF tracing or debugging settings. For example, debug compilation will be on by default until disabled: XML <compilation debug="false">
Files and assemblies Now it's time to discuss what to put in the folders you've created for the IIS application. The following assemblies are always required: IdeaBlade.Core.dll IdeaBlade.EntityModel.dll IdeaBlade.EntityModel.Edm.dll IdeaBlade.EntityModel.Server.dll IdeaBlade.EntityModel.Web.dll IdeaBlade.Linq.dll IdeaBlade.Validation.dll You'll also need the .config file: web.config Optional but strongly encouraged: global.asax Optional (and deprecated): EntityService.svc EntityServer.svc If you've developed your application using one of the DevForce supplied n-tier or Silverlight application Visual Studio templates, then the required files and references were added to your web project. You'll of course also need all of your own "server-side" assemblies. These are the assemblies holding your entity model(s), and any custom extensions you've implemented. All assemblies should be placed in the bin folder. You may place the DevForce assemblies in the GAC, although there's generally no compelling reason to do so, unless you have a large number of DevForce applications installed and they are all using the same DevForce version. All other files listed above should be placed in the application root folder. You do not need to, and in most cases should not, install DevForce to the target machine. Runtime license information is found in your model assembly and not the registry. Configuration All configuration information will be in the web.config. Database connection information You'll usually need the <connectionStrings> for any databases your application uses. (You can also use <edmKeys> instead of or in addition to connectionStrings, but they may be deprecated in the future.) Remember the credential information in the connection string: if you include a userid and password you may want to think about encrypting the connection strings section if the server cannot be secured. If you use Windows integrated security for your database connections, the account used to access the database will be the application pool identity, which if you aren't using a custom application pool will be DefaultAppPool. In this case, using a custom application pool and identity with only the system and database privileges is a good option. See the security topic for additional information on securing the database. If you are using a SQL Server Express database (for ASP.NET security for example), consider instead deploying the database to a standard SQL Server instance for improved security. If you are using a custom DataSourceKeyResolver to dynamically provide connection information, you will not need to define connection information in the config file. Logging You can use the <logging> element to set the file name and location of the debug log, or to turn logging off altogether. You also might want to archive log files. Generally other logging attributes are not needed, or used only for debugging purposes. Logging on the server is usually a good idea, since the diagnostics provided will help during testing and in resolving deployment problems. In IIS you must take extra precautions to secure the log file. A typical config file might contain the following logging information: XML <logging logFile="log\DebugLog.xml" archiveLogs="true" /> ... to create a file named DebugLog.xml in the log folder and archive files automatically. Remember in the IIS Setup above we discussed the special access requirements of the log. In IIS, unless you restrict access, anyone can browse to your debuglog.xml file and discover much more about your application than you probably want to share. To see if your debug log is browsable, just point your internet browser to it - for example http://localhost/badgolf/log/debuglog.xml. One final note: do not put the log file in the bin folder. Doing so will cause your assemblies to be repeatedly recompiled. Also see the Log and Secure topics for more information. ObjectServer Unlike other EntityServer deployments, the service, port and URL information on the <objectServer> element are not used when hosting in IIS. This is because your IIS application configuration has already determined the base address(es) to use. If you've modified any of the defaults for the <serverSettings> element, such as the allowAnonymousLogin or loginManagerRequired settings, you'll also need to include that information here. If you're using ASP.NET Security you'll need to include the appropriate configuration information too. As an example, your <objectServer> element might have something like the following when using ASP.NET security and load balancing is required: XML <objectServer> <serverSettings allowAnonymousLogin="false" loginManagerRequired="true" sessionEncryptionKey="mysecretkeyhere" useAspNetSecurityServices="true" /> </objectServer> A note on load balancing - this is supported with only the Data Center Enterprise license. You must set the same sessionEncryptionKey on every load-balanced EntityServer hosting the application to the same non-blank value. The key you use is up to you, but remember that it functions like a password. If you're planning an Azure deployment with multiple instances you'll need to set the sessionEncryptionKey. The <clientSettings> apply only to the client application. As with any DevForce server or client, you can use a WCF <system.serviceModel> section to configure communications. See the samples for more information. Other configuration files Global.asax You'll usually include a Global.asax in your application folder. The default generated for you with the Visual Studio templates registers a "virtual path provider" in the application start logic. What this does is remove the need for you to provide *.svc files for the various EntityServer services. The provider instead creates in-memory files on demand. You can also use the start logic for other actions: Publish trace messages with a call to IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable(). See the trace viewer topic for more information. Control discovery of your entity model and custom components by setting the IdeaBlade.Core.Composition.CompositionHost.SearchPatterns. .svc files You'll notice in the file list above that we called the various *.svc files, such as EntityService.svc and EntityServer.svc optional. These files, when present, control the activation of the WCF services comprising the EntityServer. If you've registered the virtual path provider in the Global.asax as discussed above, then there's no need to have physical files present, as the provider will create them virtually on demand. If you don't wish to use the provider or you have customized service activation, then you can still use the .svc files. You will need a single EntityService.svc file, and one file for each EntityServer instance your application will use. If you are using either a data source extension or custom composition context when you create the EntityManager in your client application, then it will communicate with a specific EntityServer service having those same characteristics. For example, if you're using a tenant extension of "ABC", you'll need a file named EntityServer_ABC.svc. The file contents will also need to be customized: see the samples for more information. Troubleshooting WCF is not registered with IIS - See here for information on using ServiceModelReg. "The login credentials supplied are invalid" when using ASP.NET Forms authentication - If you've moved your aspnetdb database, or used the database prior to ASP.NET 4.0, the hash algorithm may have changed. See here for information on setting the machineKey and hashAlgorithmType. "Could not load file or assembly 'App_Web_..." after making a change to one or more of the files in the application directory. 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 v4.0.xxxxx subfolder (the numbers after v4.0 can vary). Here is an example using the virtual directory name of the application: aspnet_compiler v /MyApp -c . Why must CopyLocal be true in Visual Studio? Actually, it doesn't have to be. CopyLocal must be true only for those assemblies needed by IIS to compile the web application, so generally this is only IdeaBlade.EntityModel.Web and IdeaBlade.EntityModel.Server. If you navigate to the service page and an assembly is missing, you'll see a compilation error explaining the problem. It is true, however, that when you deploy your application to the web server - where DevForce will not be installed - that you'll want all required assemblies in the bin subfolder. It's easier to see which assemblies you'll need to deploy when they're already in your bin folder during testing, which they will be when CopyLocal is true. ASP.NET settings You may need to modify several ASP.NET runtime settings to control the execution timeout and maximum request size. The default execution timeout is 110 seconds. If your application experiences operation timeouts you can modify the executionTimeout setting. The value is specified in seconds. The default maximum request size is 4MB. If your client application sends large data packets to the server, for example if saving many entities or very large entities, increase the maxRequestLength value. The value is specified in kilobytes. Here's a sample httpRuntime element with both settings modified from their defaults: XML <system.web> <httpRuntime maxRequestLength="8192" executionTimeout="130" /> </system.web> Additional Resources ASP.NET and IIS Configuration IIS Learning Center Deploy as console app The ServerConsole is provided with the DevForce installation to host the EntityServer services in a console application.
You'll usually use the ServerConsole only during development and testing when your planned deployment is as a Windows Service. The ServerConsole is a simple utility you can stop and start as needed during development to test your n-tier application with a remote EntityServer. The ServerConsole is not intended to be used in production deployments. Files and assemblies The following assemblies are always required: IdeaBlade.Core.dll IdeaBlade.EntityModel.dll IdeaBlade.EntityModel.Edm.dll IdeaBlade.EntityModel.Server.dll IdeaBlade.Linq.dll IdeaBlade.Validation.dll You'll also need the .config file: ServerConsole.exe.config You'll of course also need all of your own "server-side" assemblies. These are the assemblies holding your entity model(s), and any custom extensions you've implemented. If the ServerConsole is run on your development machine, the required IdeaBlade assemblies will already be present with the DevForce installation. Configuration Did you notice above that the configuration file used here is named ServerConsole.exe.config? This is because the executable is named ServerConsole.exe and we're following standard .NET config file naming and discovery conventions. (In Windows Server 2003, the config file should be named ServerConsole.config.) The config file will contain most of what's in your app.config file in your client application. It's usually easiest to copy that file and edit as needed. You can also use the N- Tier Deployment Tool to create the file for you. Database connection information You'll usually need the <connectionStrings> for any databases your application uses. (You can also use <edmKeys> instead of or in addition to connectionStrings, but they may be deprecated in the future.) If you are using a custom DataSourceKeyResolver to dynamically provide connection information, you will not need to define connection information in the config file. Logging You can use the <logging> element to set the file name and location of the debug log, or to turn logging off altogether. You also might want to archive log files. Generally other logging attributes are not needed, or used only for debugging purposes. Logging on the server is usually a good idea, since the diagnostics provided will help during testing and in resolving deployment problems. A typical config file might contain the following logging information: XML <logging logFile="DebugLog.xml" archiveLogs="true" />
... to create a file named DebugLog.xml and archive files automatically. Also see the Log topic for more information. ObjectServer You'll generally use the <objectServer> element to configure the service information for the EntityServer. At a minimum you'll have something like the following: XML <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService" > </objectServer> If you've modified the defaults for either allowAnonymousLogin or loginManagerRequired then you'll also need to include a <serverSettings> element with your settings. The <clientSettings> apply only to the client application. Note above that we're using port 9009, one you often see in DevForce samples, but there are no DevForce requirements for this port number, and you can pick any free port. You should, however, not change the service name. You will usually need to allow the ServerConsole to communicate through your firewall on the port you've chosen. For example, in Windows Firewall the first time you run ServerConsole.exe from a specific folder and for a particular port number you'll be prompted to unblock communications to allow the program to accept inbound communications on the port. You can also manually use your firewall software to open the port wanted, but this is less secure. See Windows Firewall documentation for more information. Although we show use of the http protocol above, you can also use net.tcp and https. If using https you'll need to create an SSL certificate and map it to the port wanted - see this blog for more information. As with any DevForce server or client, you can replace the <objectServer> configuration with a WCF <serviceModel> section which gives you complete control over the configuration of communications. See the samples. Using the ServerConsole All you need to do to use the ServerConsole is place all required files and assemblies in a folder, along with the ServerConsole.exe and ServerConsole.exe.config, and run the ServerConsole executable. As noted above you may get a firewall prompt, which you should accept, and the EntityServer services will start. The console window will display information about the service configuration, or error information if the service could not start. You can then run your client application - on the same machine or another machine on the network. Remember to make sure that its <objectServer> settings match those of the server. You can terminate the services by closing the window, or pressing either Enter or Ctrl-C within the window. Note that by default the ServerConsole "publishes" its trace messages. See the logging topic for more information. Using the n-tier starter tool One of the tools provided in your DevForce installation is a utility called the "N-Tier Configuration Starter". Such a lofty name for such a simple tool. You can use this utility to have DevForce create separate Client and Server folders containing the necessary files. The tool will also create a ServerConsole.exe.config based on your application's config file. See the walkthrough for more information. Troubleshooting You receive an AddressAccessDeniedException telling you that HTTP could not register your URL because you do not have access rights to the namespace. This is caused because the executable is not running with administrator privileges and HTTP addresses are secured resources. You have two options: 1. Run the ServerService with an administrative account. 2. Run the Netsh command line tool to register the namespace with the account. The steps are as follows: 1. Open a command prompt using Run as administrator and enter: 2. netsh http add urlacl url=http://+:9009/ user=DOMAIN\USERNAME ...where 9009 is the port you are using for the EntityServer, and DOMAIN\USERNAME is the system account to be granted access. 3. Also see http://blogs.msdn.com/drnick/archive/2006/10/16/configuring-http-for-windows- vista.aspx for more information. Deploy as Windows service The ServerService is provided with the DevForce installation to host the EntityServer as a Windows service.
Deploying the EntityServer as a Windows service is a good choice in intranet and trusted domain environments, especially if you're not familiar with IIS. You'll often find that it's easier to first test with the ServerConsole before deploying the EntityServer as a Windows service. The ServerConsole configuration and processing are identical to that of the service, yet the console is easier to setup and to diagnose early problems. Files and assemblies The following assemblies are always required: IdeaBlade.Core.dll IdeaBlade.EntityModel.dll IdeaBlade.EntityModel.Edm.dll IdeaBlade.EntityModel.Server.dll IdeaBlade.Linq.dll IdeaBlade.Validation.dll You'll also need the .config file: ServerService.exe.config You'll of course also need all of your own "server-side" assemblies. These are the assemblies holding your entity model(s), and any custom extensions you've implemented. You may place the DevForce assemblies in the GAC, although there's generally no compelling reason to do so, unless you have a large number of DevForce applications installed and they are all using the same DevForce version. You do not need to, and in most cases should not, install DevForce to the target machine. Runtime license information is found in your model assembly and not the registry. Configuration Did you notice above that the configuration file used here is named ServerService.exe.config? This is because the executable is named ServerService.exe and we're following standard .NET config file naming and discovery conventions. (In Windows Server 2003, the config file should be named ServerService.config.) The config file will contain most of what's in your app.config file in your client application. It's usually easiest to copy that file and edit as needed. Database connection information You'll usually need the <connectionStrings> for any databases your application uses. (You can also use <edmKeys> instead of or in addition to connectionStrings, but they may be deprecated in the future.) Remember the credential information in the connection string: if you include a userid and password you may want to think about encrypting the connection strings section if the server on which the ServerService is running cannot be secured. If you use Windows integrated security, rememeber that the service "log on" account will be the account accessing the database. Using a custom account for the service with only the system and database privileges needed is a good option in this case. If you are using a custom DataSourceKeyResolver to dynamically provide connection information, you will not need to define connection information in the config file. Logging You can use the <logging> element to set the file name and location of the debug log, or to turn logging off altogether. You also might want to archive log files. Generally other logging attributes are not needed, or used only for debugging purposes. Logging on the server is usually a good idea, since the diagnostics provided will help during testing and in resolving deployment problems. A typical config file might contain the following logging information: XML <logging logFile="log\DebugLog.xml" archiveLogs="true" /> ... to create a file named DebugLog.xml in the log folder and archive files automatically. Also see the Log topic for more information. Remember to secure the folder or file from prying eyes. ObjectServer You'll generally use the <objectServer> element to configure the service information for the EntityServer. At a minimum you'll have something like the following: XML <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService" > </objectServer> If you've modified any of the defaults for the <serverSettings> element, such as the allowAnonymousLogin or loginManagerRequired settings, you'll also need to include that information here. The <clientSettings> apply only to the client application. Note above that we're using port 9009, one you often see in DevForce samples, but there are no DevForce requirements for this port number, and you can pick any free port. You should, however, not change the service name. Although we show use of the http protocol above, you can also use net.tcp and https. If using https you'll need to create an SSL certificate and map it to the port wanted - see this blog for more information. As with any DevForce server or client, you can replace the <objectServer> configuration with a WCF <system.serviceModel> section which gives you complete control over the configuration of communications. See the advanced configuration topic for more information. Installing the service You must first install the ServerService as a Windows service. Do this as follows: 1. Place all required files and assemblies in a folder, along with the ServerService.exe and ServerService.exe.config 2. Launch the Visual Studio command prompt from the Windows Start menu at Start / Microsoft Visual Studio 2010 / Visual Studio Tools / Visual Studio Command Prompt. 3. Change to the folder holding your server files and assemblies 4. At the Command Prompt, run the InstallUtil tool as follows: installutil ServerService.exe 5. After running InstallUtil, the IdeaBlade DevForce 2010 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).
Once the service is running, your client applications can then communicate with the EntityServer. Remember to make sure that their <objectServer> settings match those of the server. Note that by default the ServerService "publishes" its trace messages. See the logging topic for more information. Securing the application You've hopefully taken steps to secure your application, even if it will be used only within your organization. You've disabled anonymous access, and authorize user actions. Take a look at the additional steps you can also take. Troubleshooting You receive an AddressAccessDeniedException telling you that HTTP could not register your URL because you do not have access rights to the namespace. This is caused because the executable is not running with administrator privileges and HTTP addresses are secured resources. You have two options: 1. Run the ServerService with an administrative account. 2. Run the Netsh command line tool to register the namespace with the account. The steps are as follows: 1. Open a command prompt using Run as administrator and enter: 2. netsh http add urlacl url=http://+:9009/ user=DOMAIN\USERNAME ...where 9009 is the port you are using for the EntityServer, and DOMAIN\USERNAME is the system account to be granted access. 3. Also see http://blogs.msdn.com/drnick/archive/2006/10/16/configuring-http-for-windows- vista.aspx for more information. Advanced configuration options Last modified on March 23, 2011 23:59 Contents N-tier client o Default configuration o Customizing configuration EntityServer o Default configuration o Customizing configuration Controlling compression Additional resources The EntityServer is composed of several WCF services. DevForce usually handles the WCF configuration for you on both client and server, relieving you of the need to understand the complexities of WCF. But in some circumstances you may want, or need, to take control over this configuration. Below we'll describe the configuration details and how you can customize them for advanced scenarios.
N-tier client By default, DevForce will use the <objectServer> information in your config file to configure communications from the client to the EntityServer. In Silverlight applications you won't usually have an app.config file, so the <objectServer> settings are defaulted based on the URL from which the XAP was downloaded. You can also directly set objectServer properties on the IdeaBladeConfig.Instance at startup if you don't wish to use a config file. Default configuration Before diving into customization, let's get a better understanding of the default configuration DevForce uses. The rules are fairly simple: 1. Communications will be established for a specific "service name". This is usually either "EntityService" or "EntityServer". When a data source extension or custom composition context is used then the EntityServer name will also contain that information, for example "EntityServer_dsext+ccname". 2. If a WCF <system.serviceModel> section is found in the config file (ServiceReferences.ClientConfig in Silverlight) with an endpoint for the particular service name, then a ChannelFactory is built from this configuration information. 3. If the <system.serviceModel> information was not present or invalid then programmatic configuration is performed. 1. The Address is built from the information in the <objectServer> element, formatting a URI from the RemoteBaseUrl, ServerPort and the specific service name. In Silverlight, "/sl" is appended to the address if not already present, since the default EntityServer endpoints all use this postfix to indicate Silverlight-specific endpoints. 2. The Binding is built, based on the protocol scheme in the RemoteBaseUrl. A CustomBinding is built from the following: 1. The GZipMessageEncodingBindingElement to provide compressed binary messages. 2. A transport binding element appropriate to the protocol: http - HttpTransportBindingElement https - HttpsTransportBindingElement net.tcp - TcpTransportBindingElement. In Silverlight this binding requires the System.ServiceModel.NetTcp assembly, so DevForce does not provide programmatic configuration by default; you can still use tcp via the serviceModel. net.pipe - NamedPipeTransportBindingElement. Not supported in Silverlight. 3. Whatever the transport, the MaxReceivedMessageSize is set to int.MaxValue, and except in Silverlight, the ReaderQuotas are set to int.MaxValue for MaxArrayLength, MaxDepth and MaxStringContentLength. The ReaderQuotas are not supported by Silverlight. 3. The Contract is one of the DevForce service contracts, indicating either the EntityService or EntityServer. 4. The ChannelFactory is built from this information. 4. Except in Silverlight, a DataContractSerializerOperationBehavior is added to all contract operations to specify the serializer to use (DCS or NDCS). 5. The ServiceProxyEvents OnEndpointCreated and OnFactoryCreated methods are called and the proxy is opened. See below for more information on the ServiceProxyEvents. Customizing configuration We saw above that either the <system.serviceModel> determines the configuration or it's built programmatically. There are two ways to to customize this. 1. Use the WCF <system.serviceModel> section to configure client communications. See the samples on working with the serviceModel in client applications. 2. Use a custom ServiceProxyEvents to modify existing configuration. As we saw above, methods on this class are called after the ChannelFactory is built, regardless of how it is built. See the samples for a detailed description and examples. In some cases you may want to use both approaches at the same time. For example, a <system.serviceModel> to configure the communications elements needed, and then a ServiceProxyEvents to inject additional runtime-specific behaviors. In Silverlight, configuration options via the ServiceReferences.ClientConfig are limited, so doing additional modifications, as permitted by Silverlight, through a ServiceProxyEvents can be helpful. See the sample on adding Gzip support for an example. EntityServer DevForce will create a least two WCF services, one EntityService and one or more EntityServers. The EntityService functions as a gateway to an EntityServer: a client application will handshake with it, and then all further communications it makes will be with an EntityServer. If you are using either a data source extension and/or custom composition context when you create an EntityManager in your client application, then it will communicate with an EntityServer service having the same characteristics and whose name reflects these settings. Default configuration The same configuration rules are following for both EntityService and EntityServer services. Configuration is also independent of hosting type except where noted. 1. Determine the service name. This is usually either "EntityService" or "EntityServer". When a data source extension or custom composition context is used then the EntityServer name will also contain that information, for example "EntityServer_dsext+ccname". 2. Determine the base address(es) of the service. 1. When hosted in IIS, the IIS application name and web site bindings determine the base addresses. For example, if both http and https bindings are enabled for a web site, then a base address is supplied by IIS for each. 2. When hosted by either the ServiceService or ServerConsole: 1. If <baseAddresses> are defined for the service in the config file then they will be used, 2. Otherwise information in the <objectServer> element is used, formatting a URI from the RemoteBaseUrl, ServerPort and the specific service name. 3. If a WCF <system.serviceModel> section is found in the config file with a <service> element for the particular service name, then it will be used to configure endpoints and behaviors. For the EntityServer, a service element named "EntityServer" will be used if present and no element exists for the specific service name (e.g., "EntityServer_ABC"). This allows the single service configuration to serve as a template for all EntityServer services which might be used, while still allowing each EntityServer to use custom configuration when wanted. 4. Add service behaviors: 1. A behavior is added for the DevForce error handler to create DevForce-specific message faults. 2. Set AspNetCompatibilityRequirementsAttribute to allowed. 3. Add a ServiceThrottlingBehavior to set the MaxConcurrentCalls and MaxConcurrentSessions to 1000 for http/https and 100 for tcp. 4. Add a DataContractSerializerOperationBehavior to all contract operations to specify the serializer to use (DCS or NDCS). 5. If no endpoints were found for the service in the <system.serviceModel> then DevForce will add one or more endpoints for each base address. The SupportedClientApplicationType on the <serverSettings> determines which endpoints are added. This setting defaults to UseLicense, which means that endpoints will be added for whatever your license allows. 1. The Address is the base address. 2. The Binding is built based on the protocol scheme for the address. A CustomBinding is built from the following: 1. The GZipMessageEncodingBindingElement to provide compressed binary messages. 2. A transport binding element appropriate to the protocol: http - HttpTransportBindingElement https - HttpsTransportBindingElement net.tcp - TcpTransportBindingElement. This is not used for Silverlight endpoints. net.pipe - NamedPipeTransportBindingElement. This is not used for Silverlight endpoints. 3. Whatever the transport, the MaxReceivedMessageSize is set to int.MaxValue, and the ReaderQuotas are set to int.MaxValue for MaxArrayLength, MaxDepth and MaxStringContentLength. 3. A ServiceEndpoint is added using the base address, the binding created and the contract for the service. For Silverlight endpoints, a relative address of "/sl" is added. 4. The ServiceHostEvents OnEndpointCreated is called to allow the endpoint to be customized. 6. For Silverlight endpoints, an endpoint behavior is added for the SilverlightFaultBehavior to allow faults to be sent to the Silverlight client. DevForce considers an endpoint a "Silverlight endpoint" if it contains the "/sl" relative address. 7. After all endpoints are processed ServiceHostEvents OnServiceHostCreated is called to allow customization of the service and all endpoints before the ServiceHost is opened. After all these efforts, you'll see messages in the server's log indicating the service name and the addresses it's listening on. For example, the following log entries show that the EntityService is listening on four endpoints. Two base addresses were supplied by IIS, http://myhost/BadGolf and https://myhost/BadGolf becasue both http and https bindings are set for the web site; and two endpoints were created for each base address, an endpoint for Silverlight client applications and one for all other applications.
Customizing configuration We saw above that either the <system.serviceModel> determines the configuration or it's built programmatically. There are two ways to to customize this. 1. Use the WCF <system.serviceModel> section to configure the service and its endpoints. See samples on working with the serviceModel on the server. 2. Use a custom ServiceHostEvents to modify existing configuration. As we saw above, methods on this class are called after each programmatic endpoint is added, and again after the ServiceHost is built. See the samples for a detailed description and examples. In some cases you may want to use both approaches at the same time. For example, a <system.serviceModel> to configure the communications elements needed, and then a ServiceHostEvents to inject additional runtime-specific behaviors. As we saw above you can also control the endpoints created using the SupportedClientApplicationType setting in the <serverSettings>. For example, if your application will only have Silverlight clients yet you have an Enterprise license, you can set the appropriate SupportedClientApplicationType setting so that only Silverlight endpoints are created. Controlling compression By default all communications between the n-tier client and EntityServer are compressed. You can control the level of compression - to prefer speed over compression factor or vice versa - to fine tune application communications. To set the compression level, use the CommunicationSettings class. For example: C# IdeaBlade.Core.Wcf.Extensions.CommunicationSettings.Default.CompressionLevel = Ionic.Zlib.CompressionLevel.BestSpeed; VB
IdeaBlade.Core.Wcf.Extensions.CommunicationSettings.Default.CompressionLevel = Ionic.Zlib.CompressionLevel.BestSpeed The CompressionLevel enumeration has a range of possibilities from BestCompression to BestSpeed. It also supports turning off compression, although if you wish to turn off compression it's more efficient to change the bindings used. On the EntityServer the CommunicationSettings.Default.CompressionLevel should be set in the global.asax when hosted in IIS; on the client you can set this in the application startup code. You can actually change the CompressionLevel at any time while your application is running and the new level will be used for all further communications, but generally you should set this early in your startup logic. The level can also differ between client and server. Additional resources The ABCs of Endpoints
Troubleshooting n-tier Last modified on March 31, 2011 13:24 Contents Cannot connect to EntityServer Known type or serializer errors How to see if the EntityServer is running Error occurred while trying to make a request ... Mismatched IdeaBlade assemblies Also see An n-tier application is inherently a bit more complex to configure and deploy. Here we'll try to help with troubleshooting common problems.
Cannot connect to EntityServer You've received a message such as "The remote server returned an error: NotFound" or possibly "There is no endpoint listening on http://localhost:9009/EntityService.svc/sl that could accept calls from this application" (where the address is the address of your EntityServer). Both messages aren't quite as scary as they sound, and indicate that the client is unable to communicate with the server, often for a very simple and easily corrected reason. You'll usually, but not always, receive additional information with the error message which attempts to provide some helpful information on the cause of the failure and possible solutions. Take a moment to read that information to see if it applies. Also follow these troubleshooting steps: For Silverlight applications while debugging and testing in Visual Studio, the web project must be the startup project. Make sure the service is running. If you've included a <system.serviceModel> or <objectServer> information in your client config, make sure that all information is correct, and matches what the server is using. Double check the address by trying to browse to it to see if the service is running. Check the debuglog on the server. It will indicate the endpoints the server is listening on.
Known type or serializer errors Every type which will be moved across tiers must be defined on both tiers, serializable, and a known type. See the known type primer for more information. How to see if the EntityServer is running Regardless of how the EntityServer is hosted, you can always navigate - with your internet browser - to the "gateway" EntityService. The URL will differ based on the host, but will generally resemble one of the following: When hosted in IIS: http://myserver/myapp/EntityService.svc In the ASP.NET Development web server (with port 9009): http://localhost:9009/EntityService.svc When hosted as a Windows service or console application (again using port 9009 as a sample): http://localhost:9009/EntityService What you see on this page will tell you if the service is running, and if not the error which is keeping it from running. Generally, because DevForce does not by default publish metadata, you will see a simple page that tells you the service is running but publishing is disabled:
You don't need to publish metadata, so seeing this is a good sign, and means the service is running. If instead you see any type of "server error" page, then the service has not started. The page itself will contain diagnostic information on the cause of the problem. For example, here we see the service did not start because of a bad config file (in this case the xml was invalid).
Whenever your n-tier client application cannot communicate with the EntityServer, the first thing to check is if the EntityServer is in fact running. Trust us, this will save you, and IdeaBlade Support, a great deal of time! Error occurred while trying to make a request ... If your application is able to connect to the EntityServer but later gets an error such as "An error occurred while trying to make a request to URI ...", then it's likely that one or more of the types being sent cannot be serialized for some reason. You'll need to drill into the inner exceptions to see the original cause. For example, if the error occurs during a SaveChanges, a custom IIdGenerator will be at fault if it can't be serialized. Mismatched IdeaBlade assemblies The IdeaBlade class library versions must be the same on both client and server tiers. If youve recently upgraded DevForce, you should confirm that the assemblies on both the client and server have the same version. Also see Troubleshooting an IIS Deployment Troubleshooting a Service or Console Server Debugging Known type primer
Two-tier deployment In a DevForce application, a two-tier deployment is one in which the client tier communicates directly with the data tier, without the intervention of an application server tier.
This is the simplest of the deployment options, since there are fewer "moving parts" to worry about. Generally your data tier is on one or more separate physical machines, but it need not be. Client application Without an application server, the responsibilities of the service tier are reduced, and packaged with the client application. The EntityManager will still communicate with an EntityServer to authenticate users and query and save entities, but the EntityServer will be deployed locally, not as a service but as part of the AppDomain in which the application runs. Your client tier might be a WinForms, WPF or console application, or a Windows Service. An ASP.NET application which does not require that the EntityServer be exposed as a service can also be considered a two-tier client application. Files and assemblies The following assemblies are always required: IdeaBlade.Core.dll IdeaBlade.EntityModel.dll IdeaBlade.EntityModel.Edm.dll IdeaBlade.EntityModel.Server.dll IdeaBlade.Linq.dll IdeaBlade.Validation.dll You can use ASP.NET security in a two-tier application. If so, you'll also need: IdeaBlade.EntityModel.Web.dll You'll usually need your .config file too: app.exe.config (or web.config) You'll of course also need all of your own assemblies and other files needed by your application. You may place the DevForce assemblies in the GAC, although there's generally no compelling reason to do so, unless you have a large number of DevForce applications installed and they are all using the same DevForce version. You do not need to, and in most cases should not, install DevForce to the target machine. Runtime license information is found in your model assembly and not the registry. The config file A typical configuration file will contain <connectionStrings> and <ideablade.configuration> sections, although neither is strictly required. No part of the <ideablade.configuration> section is required, and can be removed altogether if you are not using it and can rely on DevForce defaults. Connection information You'll usually need the <connectionStrings> for any databases your application uses. (You can also use <edmKeys> instead of or in addition to connectionStrings, but they may be deprecated in the future.) Remember the credential information in the connection string: if you must include a userid and password you may want to think about encrypting the connection strings section. Using Windows integrated security is often a better approach. If you are using a custom DataSourceKeyResolver to dynamically provide connection information, you will not need to define connection information in the config file. Logging You can use the <logging> element to set the file name and location of the debug log, or to turn logging off altogether. You also might want to archive log files. Generally other logging attributes are not needed, or used only for debugging purposes. A typical config file might contain the following: XML <logging logFile="log\DebugLog.xml" archiveLogs="true" />
... to place the file in a sub-folder named log and archive files automatically. Also see the Log topic for more information. ObjectServer You will not of course be communicating with a remote EntityServer, and frequently the <objectServer> element is not needed at all. You may find it useful if you wish to modify the login-related settings for allowAnonymousLogin and loginManagerRequired. Remember that the config file is not secure, so you may instead want to set these in your application code via the IdeaBladeConfig.Instance when the application starts. All other settings for the <objectServer> element and sub-elements are not pertinent to a two- tier deployment. Packaging for deployment The computers on which you deploy the application likely have the .NET Client Profile or full .NET Framework 4.0 already installed or you will install it as part of your deployment. Note that a few additional assemblies, not in the .NET Client Profile, are required by DevForce when deployed in two-tier. This is because the IdeaBlade.EntityModel.Server assembly references additional assemblies not part of the profile. These are: System.ServiceModel.Activation System.ServiceModel.Web. You should be sure to include these assemblies with your installation. Generally client applications are installed via an installer or a ClickOnce deployment.
The data tier We don't really have much to say about your database(s) and leave its configuration to you and your DBA. You should consider security, however, both in terms of connection information on the client, access accounts, and transmission security. DevForce imposes no special requirements on the data tier. Security Remember that a client application deployed to a user's workstation can be compromised. Its code can be disassembled, its configuration, log and other files peered at, and its communications with the database intercepted. See the Security topic for more information on steps you can take to secure your client application. App.config in detail DevForce applications are usually configured using an external configuration file - an app.config for desktop (and sometimes Silverlight) applications, and a web.config for ASP.NET applications and the EntityServer when hosted by IIS. With so many .config files in your projects; differences between design-time configuration and run-time configuration; differences between client and server; and further differences between development, test and deployed configurations, it's easy to be confused. Here we'll try to make some sense of these files - what they should contain, and when you should use them. Overview Your first question may be "When do I need to use a .config file?" For now, you should assume you'll need a .config file whenever you need to override DevForce's default assumptions. "But what are the DevForce defaults?" you wonder. We'll describe these defaults below in detail, but generally if you need to tell DevForce something about your application: "Where is the database? Is there a remote EntityServer? Where? Where is the log file?" then you will likely need to supply this information via a .config file. You can also programmatically supply configuration information, and in some cases supply configuration information via probed interface implementations (e.g., the IDataSourceKeyResolver for dynamic database connection information is the most common). If you create your application using one of the DevForce-supplied Visual Studio project templates, we've included simple .config files where appropriate to get you started. You'll see a web.config in the web projects of your n-tier solutions, and an app.config in the desktop projects of both 2-tier and n-tier solutions; these files will contain the necessary information to get your application running. You'll notice that the Silverlight application does not by default contain an app.config since it's usually not required, but we'll discuss that further below. As you add projects to your solution - for example a class library to hold your Entity Data Model - you may find these projects also contain an app.config file. In the case of the EDM, the app.config is used only at design time by the EDM designer. The only configuration files used at run-time - the only ones DevForce uses - will be the .config for your application executable and the web.config. As you develop your application and move from development to test to production environments your configuration will usually need to change too. For example, log file locations, database connection strings, the URL of the EntityServer services - all might differ in the different environments. You'll need to be aware of these changing needs and how to set configuration appropriate to each environment. Discovery Let's jump in and begin with the issue of discovery. DevForce does have some DevForce- specific features regarding .config discovery, and it's good to understand these features early to avoid confusion later on. Silverlight Unlike standard Silverlight applications in which the app.config (or System.Configuration) is not supported, DevForce Silverlight applications do support use of an app.config file. Only the ideablade.configuration section can be specified in the file, since standard Microsoft- supplied configuration is not supported. (Note the ServiceReferences.ClientConfig file is also supported in DevForce Silverlight applications for customization of WCF communications.) DevForce will find the file if placed in the application project, named "app.config", and given one of the following build actions: Embedded Resource to embed the file in the assembly Content to leave the file as a loose file in the XAP, accessible for modification without recompilation In most Silverlight applications the default configuration assumed by DevForce is sufficient and you will not need to supply an app.config at all. The default configuration assumes that the EntityServer is located in the same location from which the XAP was downloaded. For example, if the XAP for your application was downloaded from http://myhost/myapp/default.aspx, DevForce will default the service URL to http://myhost/myapp/EntityService.svc. If your EntityServer is located at this address, then you won't need an app.config in your Silverlight application. If your EntityServer is at a different location, then you will need to override the DevForce default; you can do this by including an app.config with the appropriate <objectServer> information. Changing the default with an app.config is optional -- you can also do this programmatically, or via a ServiceReferences.ClientConfig file. A sample Silverlight app.config is provided in the deployment snippets available with the downloaded code. Desktop / ASP.NET In non-Silverlight applications, DevForce follows a slightly different probing path to find the app.config than standard .NET applications. Standard .NET applications use only the applications config file MyApp.exe.config, or web.config for a web application. In DevForce, the search for configuration information is as follows, and continues until a valid configuration is found: 1. If the ConfigFileLocation property of the IdeaBladeConfig object has been set in the executing code, DevForce will search the indicated location for a file named or matching *.exe.config (or web.config if a web project). If not found, other *.config files in the folder are searched for a valid ideablade.configuration section. IdeaBladeConfig.ConfigFileLocation = @"c:\myapp"; 2. If the IdeaBladeConfig.ConfigFileAssembly property has been set, DevForce will look for an embedded resource named app.config in the specified assembly. IdeaBladeConfig.ConfigFileAssembly = Assembly.GetExecutingAssembly(); 3. Next, the current executable/bin folder is searched for a file named or matching *.exe.config (or web.config if a web project). If not found, other *.config files in the folder are searched for a valid ideablade.configuration section. 4. DevForce next searches for an embedded resource named app.config in the entry assembly. 5. If a valid Ideablade.configuration section was not found in any of the above locations then DevForce will create a default IdeaBladeConfig instance. In some applications this default instance may be sufficient, but check your debuglog.xml if you find that your configuration is not being used. Caution: If you rely on DevForce-specific discovery - using an embedded resource or a loose config file not identified by .NET as the config file for the AppDomain - non-IdeaBlade sections of the config file will not be found by .NET. Note that in a test project, such as one created with MSTest, if you have enabled deployment you should also ensure that a loose config file is deployed, or set either the IdeaBlade.ConfigFileLocation or IdeaBlade.ConfigFileAssembly properties, since standard DevForce probing may not work as expected. Configuration contents and defaults Now that we know how to find a .config file, what should we put in it? DevForce defines the ideablade.configuration section to hold all IdeaBlade-specific configuration information. At run-time, this information is used to load the single in-memory instance of the IdeabladeConfig class, available via the IdeaBladeConfig.Instance static property. You may modify many properties of the IdeaBladeConfig at run-time, although this should be done before your application begins using other DevForce features. Programmatic run-time configuration can be accomplished by modifying IdeaBladeConfig.Instance. The ideablade.configuration section includes the following child elements. Element Description (Root)
objectServer Governs access to the EntityServer service in an n-tier deployment. Contains clientSettings and serverSettings child elements to specify configuration specific to either client or server. logging Identify where and how to write the DebugLog. probeAssemblyNames Deprecated. Allows the developer to specify assemblies to be used in the discovery of custom implementations of DevForce interfaces. Any assembly names specified here supplement the default DevForce discovery options. edmKeys Optional configuration data applicable to one or more Entity Data Model data sources. There may be several named <edmKey/> tags if the application uses more than one Entity Data Model. DevForce will use information in the <connectionStrings> element to discover data source information, but you may specify an EdmKey to override that discovery. verifiers Used to define verifiers external to your application code. See the Validation topic for more information. notificationService Used to define settings for the push notification feature. Root element In order to include the ideablade.configuration section in your config file you must "register" the section. You do this by placing a section element in the configSections, like so: XML <configuration> <configSections> <section name="ideablade.configuration" type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core" /> </configSections> </configuration> The configSections should be at the top of the configuration, so that the section registrations take place before the actual section. The ideablade.configuration section looks like the following. It can go anywhere in the config file after the configSections definitions. Providing the namespace ensures that you can use Intellisense while editing the config in Visual Studio. XML <ideablade.configuration version="6.00" xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig"> </ideablade.configuration> ObjectServer Element Settings controlling data service features, security, and communications to the EntityServer. XML <objectServer remoteBaseURL="http://localhost" serverPort="9009" serviceName="EntityService" useDCS="true" > <clientSettings isDistributed="false" /> <serviceKeys> <serviceKey name="BOS2" remoteBaseURL="http://somehost" serverPort="80" serviceName="myapp/EntityService.svc" /> </serviceKeys> <serverSettings allowAnonymousLogin="true" loginManagerRequired="false" sessionEncryptionKey="" supportedClientApplicationType="UseLicense" useAspNetSecurityServices="false" userSessionTimeout="30" /> </objectServer> Attribute Description remoteBaseURL The protocol and machine name (or IP address) used to form the full URL. serverPort The port the EntityServer is listening on. serviceName The name of the entry point service. This is generally "EntityService" when not hosted by IIS; when hosted in IIS the name consists of both the ASP.NET application name and the service file "EntityService.svc", for example, "myapp/EntityService.svc". useDCS Advanced. Controls the serializer in use for client-server communications in n- tier. Generally you should not set this field but allow DevForce to choose a default. In Silverlight applications only "DCS", the DataContractSerializer, may be used. In Desktop applications, the DCS serializer is also used by default, but you can instead use the "NDCS", or NetDataContractSerializer, by setting this flag to false. The remoteBaseURL, serverPort and serviceName attributes are used to determine the "endpoint" address of the server when running in an n-tier configuration. These settings are common to both client and server. When running in IIS, or if using a system.serviceModel section to configure WCF services, you do not need to provide these fields. A full URL, when built from these fields, might look something like "http://localhost:9009/EntityService" or "http://localhost/MyApp/EntityService.svc". (Of course "localhost" is used only during development when both client and server are on the same machine.) ClientSettings element The clientSettings apply only to the client. Child element of the objectServer element. Attribute Description isDistributed Determines whether the client will use a remote EntityServer. When enabled, the client will communicate with an application server tier; when disabled, the client performs its own data service operations. ServiceKeys element The serviceKeys apply to the client, but may be used on the server in some situations as defined below. A serviceKey provides the address information for a single application server; a client application may communicate with multiple application servers. Child element of the objectServer element. ServiceKey attributes Attribute Description name Identifies the service key. remoteBaseURL The protocol and machine name (or IP address) used to form the full URL. serverPort The port the EntityServer is listening on. serviceName The name of the entry point service. This is generally "EntityService" when not hosted by IIS; when hosted in IIS the name consists of both the ASP.NET application name and the service file "EntityService.svc", for example, "myapp/EntityService.svc". The remoteBaseURL, serverPort and serviceName attributes are used to determine the "endpoint" address of a server when running in an n-tier configuration. These attributes serve the same purpose on the serviceKey as they do when defined on the <objectServer> element itself. You use one or more <serviceKey> definitions when your client application may communicate with multiple application servers. The <serviceKeys> are not usually defined on the server, since their intention is to provide for multiple named servers, but they can be used there. If the EntityServer is deployed as either a console application or Windows service then DevForce must determine the "base address" of the EntityServer. It will use the attributes on the <objectServer> if present, otherwise it will look for a key named "default", and if not present use the first serviceKey defined. ServerSettings element These settings concern server configuration. In a 2-tier application without an application server tier you would include any serverSettings needed in the application config file. The descriptions below indicate if a setting is specific to n-tier. Child element of the objectServer element. Attribute Description allowAnonymousLogin Determines whether "guest" users are allowed by the application. These users can use the application without supplying login credentials. Default is true. loginManagerRequired Determines whether the application will function if a "login manager" cannot be found. When not using ASP.NET security features you must provide an IEntityLoginManager implementation if this flag is true. Default is false. sessionEncryptionKey In an n-tier application which requires load balancing you must set this key to a non-blank value. The key is used to encrypt information in the token passed between client and server. Data Center license only. supportedClientApplicationType This is used in an n-tier application to allow the application server tier to correctly initialize WCF communications. By default DevForce will use your license to determine which application types to support. You may override this setting, as long as the chosen value is consistent with your license. For example, you might have a Universal license but a given server might support only Silverlight clients, or only WinClient clients. By default, if you have a Universal license communications for both types of clients will be initialized. useAspNetSecurityServices Determines whether ASP.NET security is used for user authentication. By default this flag is off. userSessionTimeout Sets the number of minutes of inactivity after which a client session is removed from the server's session map. Default is 30 minutes. Logging Element Controls the logging of run-time debug and trace information applicable to any configuration. XML <logging logFile="DebugLog.xml" archiveLogs="false" shouldLogSqlQueries="false" port="9922" serviceName="TracePublisher" usesSeparateAppDomain="false" /> Attribute Description logFile The name of the file containing tracing and debugging information. You can set the field to an empty string to turn off default logging. You can also supply a relative or full path name. By default the log file is written to the exe folder in Desktop applications and to a subfolder named "log" in ASP.NET applications. Silverlight applications do not create a physical log file. archiveLogs Defaults to false. Turn this on to archive previous log files. shouldLogSqlQueries Whether to log the generated SQL for queries. This is a global setting which applies to all data sources. If an EdmKey logTraceString attribute is false (the default), logging will still occur when shouldLogSqlQueries is true. port This is the port used by the TracePublisher service when "remote" publishing is enabled. By default port 9922 is used, but if you have multiple publishers on the same machine you need to provide either a unique port or serviceName for each. serviceName The name of the TracePublisher service when "remote" publishing is enabled. By default this name is "TracePublisher". usesSeparateAppDomain This is an advanced feature which allows the logger to be run in a separate application domain. By default this value is false and logging takes place within the same application domain as the publisher. ProbeAssemblyNames Element By default DevForce uses MEF (the Managed Extensibility Framework) to discover exported implementations of custom features. In Silverlight applications, discovery uses the assemblies in the main XAP. In all other applications, discovery uses the files in the exe/bin folder. If the default discovery is insufficient you can specify additional assembly names here to be included when "probing". It bears repeating if you're familiar with prior DevForce generations: there's generally no reason to specify the probe assemblies in DevForce 2010. The exception is design time support for the Blend and Cider designers: if some situations you may need to specify probe assembly names to use some persistence features such as the EntityCacheState and EntityManager. XML <probeAssemblyNames> <probeAssemblyName name="MyAssembly" /> </probeAssemblyNames> Sub-element Description probeAssemblyName Name of an assembly to be probed. In Silverlight, this should be a fully- qualified assembly name. If you do find you need or want to specify the probe assembly names here, there are some naming rules to be aware of: In Desktop and ASP.NET applications, you can use the assembly simple name; i.e., the assembly file name less the .DLL or .EXE extension. For example, for the DomainModel.dll assembly, use DomainModel. In Silverlight applications, the assembly display name should be used. The display name includes the assembly simple name (defined just above), version, culture and public key token. For example, the assembly display name for the DomainModel.dll assembly might look like the following: DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null The public key will be non-null if you have signed the assembly. The version number is the assembly version defined in your code. EdmKeys Element An EdmKey is used to uniquely identify a datasource in DevForce. By default DevForce will build in-memory EdmKeys based on information in the <connectionStrings> element in the server's config file. You can specify one or more EdmKeys here to override this behavior. If the same key name is defined in both the EdmKeys and connectionStrings, the EdmKey specification will be used. XML <edmKeys> <edmKey name="Default" connection="metadata= ....." tag="some info here" logTraceString="false" /> </edmKeys> EdmKey attributes Attribute Description name Identifies the data source. If you are using data source extensions the name should contain the extension. For example a key named "Default_ext1" would be used for the "ext1" data source extension with the "Default" data source. connection The complete EF connection string. This is the same as the connectionString attribute in the <connectionStrings>. tag Can be used to provide additional information. logTraceString Can be used to enable logging of generated SQL queries. This defaults to false, but can be turned on when debugging is needed. The shouldLogSqlQueries flag on the logging element overrides the setting on the EdmKey. Verifiers Element Verifiers defined in the .config file are discovered by calling VerifierEngine.DiscoverVerifiersFromConfig . XML <verifiers> <verifier name="SampleDateTimeRangeVerifier" verifierType="IdeaBlade.Validation.DateTimeRangeVerifier, IdeaBlade.Validation" applicableType="DomainModel.Employee, DomainModel" executionModes="InstanceAndOnBeforeSetTriggers" errorContinuationMode="Stop"> <verifierArgs> <verifierArg name="propertyName" value="BirthDate" /> <verifierArg name="minValue" value="1/1/1965" /> </verifierArgs> </verifier> </verifiers> Verifier attributes Attribute Description name Name of the verifier. description Optional description used in status messages. verifierType The assembly-qualified name of the verifier applicableType The assembly-qualified name of the object type on which the verifier is defined. executionModes The conditions under which the verifier will execute. errorContinuationMode Determines the action to take after a verification error. sortValue The order in which the verifier will be executed within a verifier batch. tag Can be used to provide additional information. verifierArgs Sub-element defining any arguments for the verifier. VerifierArg attributes The verifier arguments are specific to the type of verifier. Attribute Description name Name of the argument. value Value of the argument. NotificationService Element Used to configure the notification or "push" service. XML <notificationService enabled="false" clientPort="1001" /> Attribute Description enabled Defaults to false. This is a server-side setting which allows the Notification Service to be enabled on the server. clientPort Specified in the client-side configuration to indicate the client's port which will receive pushed messages on the duplex channel. Not applicable to Silverlight clients. Configuration Editor So how to edit your app.config or web.config? You can edit directly in Visual Studio, and Intellisense will help guide you in the elements and attributes available, but if you'd like a more structured editor we've also provided the DevForce Configuration Editor to help in editing the ideablade.configuration section. The editor provides descriptive help, and can eliminate many simple errors such as misspellings. You can use the editor to modify an existing config file or create a new one.
Customize startup and shutdown Last modified on March 29, 2011 16:19 Regardless of whether your application is a two-tier application performing its own persistence operations, or an n-tier application with a separate application server tier, you might have the need to do some custom processing when the singleton EntityService starts up and again when it shuts down. What is the EntityService We don't often mention the EntityService when discussing the application server tier, but you've likely seen the name used throughout these pages. You've seen it in the <objectServer> element in your config file. You'll see it when you customize WCF configuration with a <serviceModel> definition. The EntityService functions as little more than a gateway or simple router: its primary purpose is to inform a requesting client which EntityServer it will be working with, and to either start that EntityServer or ensure it's already running. It's the EntityServer which is responsible for all persistence and security activities. These actions occur whether the application is a standalone application communicating directly with the data tier, or the EntityServer has been deployed to an application server tier. The EntityServiceApplication The IdeaBlade.EntityModel.EntityServiceApplication allows a developer to inject custom logic when the EntityService starts, and again at shut down. Here's the simple implementation: public virtual void OnServiceStartup(object sender, ServiceStartupEventArgs e) public virtual void OnServiceShutdown(object sender, EventArgs e) Your custom EntityServiceApplication is found during standard discovery. At startup, your application might need to launch auxiliary "server-side" processes for example, or perform other one-time actions. At shutdown, the application can run "server-side" clean-up code. For example, it might shut down the auxiliary services it started or send an email alert reporting that the service is coming down. Note that you cannot use EntityServiceApplication to customize the WCF configuration. Instead use the IdeaBlade.EntityModel.Server.ServiceHostEvents for this purpose. Connect to data sources You have several options on how your application will connect to the data source. We'll discuss them here.
Data source keys DevForce uses the concept of a Data Source Key to identify a data source. The key is used both at design-time, when the name of the DataSourceKey is tied to the generated code for an entity model, and again at run time when the EntityManager will query and save entities from the entity model. In the EDM Designer When you created your entity model in the Visual Studio EDM Designer, you specified a DataSourceKey for the model.
The EntityManager Name and DataSource Key name do not have to be the same. They often are in samples, since they both default to the Entity Container Name supplied in the EDM Designer. This key name is also written into the generated code, to associate the entities in a model with this schema name: C# [IbEm.DataSourceKeyName(@"NorthwindIBEntities")] public partial class Employee : IbEm.Entity { .. } VB <IbEm.DataSourceKeyName("NorthwindIBEntities")> Partial Public Class Employee Inherits IbEm.Entity End Class By default, the DataSourceKey is given the same name as the Entity Container Name, and thus points to the connection string the designer adds to the config file in your model project: XML <connectionStrings> <add name="NorthwindIBEntities" connectionString="" /> </connectionStrings> At run time The EDM Designer created the connection string from a database used at design time. In the early phases of development, you'll likely use this same database, and connection string, for your development and testing. As you query and save entities, DevForce uses the DataSourceKeyName to help find the connection information to the run time database. The connection information must be in the configuration file used by the EntityServer. If your entity model was generated into a class library (which is recommended), the app.config the EDM generated for design-time support won't ever be found or used at run time. So, your first task is ensuring that you copy or add the connection information to the run time config file. In an n-tier application, this config file will be on the server, since an n-tier client application does not directly work with the data tier. See the topic on config file discovery for more information on how the configuration file is found at run time. We're not done yet, though. DevForce uses two pieces of information to find the appropriate run time connection information: the data source key namecombined with the data source extension, which we'll discuss next. Data source extensions You may not have realized it, but when you constructed your EntityManager you also supplied it with a data source extension. The default extension is an empty string, meaning of course that no extension is used. An extension allows an EntityManager to target a specific run time database: for example it might be instructed to use "Dev" or "Test", or the extension might indicate that the EntityManager should use the databases for a particular tenant in a multi-tenant application. DevForce uses the DataSourceKeyName to identify the data source schema, combined with the data source extension to indicate the specific schema-matching database to use. It's these two pieces of information which DevForce uses when searching for the "connection information" in your configuration file. For example, if we construct an EntityManager with the "Test" data source extension: C# manager = new NorthwindEntities(dataSourceExtension: "Test"); VB manager = New NorthwindEntities(dataSourceExtension:= "Test") DevForce will use the data source key, in this case "NorthwindIBEntities", combined with the data source extension of "Test" to search the configuration for the best match. But what specifically is it searching for? We cover that next. EdmKeys vs. ConnectionStrings Above we used the vague term "connection information". We did this intentionally, since there are two ways you can provide connection information to DevForce in a configuration file: The first is with connectionStrings. These are the .NET standard way of defining database connection information in a configuration file. With a connectionString element, you supply a name and a connectionString containing the appropriate Entity Framework connection information. The second method is using a DevForce EdmKey. An edmKey contains connection information, as a connectionString will, but also additional DevForce attributes, such as logTraceString, which writes the EF-generated SQL query to the debug log. In the sample above, DevForce will search for an edmKey or connectionString named "NorthwindIBEntities_Test". The underscore is the separator character DevForce uses, and allows you to construct multi-level extensions too. Remember not to use the underscore in the data source extension you pass into the EntityManager however. DevForce will search for the best match. If you have both an edmKey and connectionString with the same name, DevForce will give preference to the edmKey. If DevForce can't find an exact match, it will drop the last extension from the string and then continue the search, and so on for all extensions provided. Note that the search is not case sensitive. If a match isn't found, you'll receive an error when attempting to query or save. Unless providing connection information dynamically, you must ensure that either an edmKey or connectionString is available at run time in the appropriate config file. Dynamic connection strings DevForce also allows you to omit connection information from the run time configuration altogether and supply the connection string dynamically, using the IDataSourceKeyResolver interface. DevForce actually uses its own implementation of the IDataSourceKeyResolver called the DefaultDataSourceKeyResolver to perform the key lookup we described above. You can implement a key resolver to override the default processing. The IDataSourceKeyResolver interface defines a single method, GetKey, which you'll implement to provide your own key resolution logic. You should return either an in-memory EdmKey or ClientEdmKey , or null if you want default key resolution to be performed. C# IDataSourceKey GetKey(String keyName, String keyExtension, bool onServer); VB IDataSourceKey GetKey(String keyName, String keyExtension, bool onServer) Note that the EdmKey you return is constructed only in code and does not need to be defined in the configuration file. Although the key resolver is called on both client and server, key resolution is required on only the server. If you implement a custom resolver it's not required that you deploy your custom class to the client. Anatomy of a connection string Regardless of how you specify your connection information - whether in the connectionStrings, edmKeys or dynamically - you must still provide a valid Entity Framework connection string. Here's an entry defined in the connectionStrings section: XM L <connectionStrings> <add name="NorthwindIBEntities"
connectionString=" metadata=res://*/DomainModel.csdl|res://*/DomainModel.ssdl|res://*/DomainModel. msl; provider=System.Data.SqlClient; provider connection string= " Data Source=.; Initial Catalog=NorthwindIB; Integrated Security=True; MultipleActiveResultSets=True; "" providerName="System.Data.EntityClient" /> </connectionStrings> The <connectionString> has three parts: name - We discussed the name and how DevForce performs lookup above. connectionString - The Entity Framework connection string, also consisting of three parts: o metadata - The location of the metadata artifact files. Note above that an EDMX named "DomainModel" was used. o provider - The database provider to use for persistence operations (here it is SQL Server). o provider connection string - The provider-specific connection information, usually indicating the server, database and login information. providerName - This is the EF provider, and is optional. Here's the same connection information specified in an EdmKey: XM L <edmKeys> <edmKey name="NorthwindIBEntities" connection=" metadata=res://*/DomainModel.csdl|res://*/DomainModel.ssdl|res://*/DomainModel. msl; provider=System.Data.SqlClient; provider connection string= " Data Source=.; Initial Catalog=NorthwindIB; Integrated Security=True; MultipleActiveResultSets=True; "" /> </edmKeys>
As you can see, the connection contains the same information as the connectionString above. Note that the sample SQL Server connection string above is more common to a developer's machine than a production deployment.
For more information on connection strings in EF, see http://msdn.microsoft.com/en- us/library/cc716756.aspx. Security and connection strings If you specify your connection information in a .config file, you may want to protect those sections containing sensitive information. You can encrypt configuration sections, such as the connectionStrings and ideablade.configuration sections, to limit unauthorized viewing. See here for more information. (To encrypt the ideablade.configuration section, make sure that the section definition contains the fully-qualified assembly name.) In a production deployment, be sure to use a database account with appropriate privileges. Don't use an administrative account, and if you do use integrated security make sure that the service account does not have administrative privileges. Also consider encrypting database traffic. See here for more information on encrypted SQL Server connections.
You can use the Configuration Editor in two different ways: 1. Launch it from the DevForce 2010 folder in the Windows Start menu. 2. Configure it to work within Visual Studio. To do this: 1. Select the app.config file, right-click, and select Open With option. 2. If you do not see ConfigEditor.exe in the list of programs, click the <Add> button. 3. On the Add Program dialog, click the ellipsis button to browse to a file. Navigate to the DevForce installation directory (typically C:\Program Files\DevForce 2010) and select the file ConfigEditor.exe from the Tools subfolder. Give it any Friendly Name you wish; e.g., DevForce Configuration Editor. 4. Once DevForce Configuration Editor is in the list, double-click it to open the configuration file in that editor.
Configure programmatically You can provide DevForce configuration information programmatically by setting properties of the I deaBladeConfig.I nstance singleton, either on the client or on the server.
Many DevForce components require configuration. A remote client, for example, must be configured with the address of the server. The DevForce EntityServer needs to know the connection string of the database. The "Configure and Deploy" topic describes many ways to provide configuration information such as adding Ideablade sections to Web.config and App.config files and creating custom DataSourceKeyResolvers. This topic describes the IdeaBladeConfig in-memory representation of DevForce configuration. IdeaBladeConfig.Instance At runtime, DevForce creates an in-memory representation of configuration information in a static "singleton" object called the IdeaBladeConfig.Instance . DevForce components reference this in-memory singleton when they need configuration data such as ObjectServer.ClientSettings.IsDistributed: Whether the client is running 2-tier (false) or n-tier (true) The URL to the remote server The Entity Data Model (EDM) keys (EDM Keys) that hold the database connection strings ... much more ... You can inspect configuration values in your code via the static IdeaBladeConfig.Instance property. You can update them as we will see. Both the client application and the server have their own instances of IdeaBladeConfig. They may share some configuration in common such as the assembly names to probe for custom classes. Some configuration is meaningful only on the client such as the URL of the server. Some configuration is meaningful only on the server such as database connection strings. We use one object type, the IdeaBladeConfig, for both environments. Client and server functionality execute in the same process in a 2-tier deployment and their respective configuration data are held in a single IdeaBladeConfig instance. Where IdeaBladeConfig gets its values DevForce populates the IdeaBladeConfig from values in the XML configuration file that it discovers. It doesn't actually try to populate an IdeaBladeConfig until you or a DevForce component asks for configuration information. This just-in-time behavior gives you the ability to specify where to get external configuration data by setting certain static properties on the IdeaBladeConfig class: Property Description ConfigFileLocation Directory to search for configuration files ConfigFileName Local path and filename of the configuration file to use ConfigFileAssembly Assembly to search for an embedded These setting come into play the moment you retrieve the IdeaBladeConfig.Instance. DevForce discovers the configuration information (if any) and populates the in-memory IdeaBladeConfig object. You will notice in the examples below that many of the IdeaBladeConfig object values are not set at all. That's because DevForce didn't find any pertinent IdeaBlade XML sections in any of the configuration files it searched. That's quite normal. When a component requests a configuration value and there is none, DevForce resorts to default behaviors to discover and construct the unspecified values. We'll see an example of that below with regard to determining a database connection string. DevForce prefers configuration values to its own calculated defaults. You can set configuration values as well as read them. If you set configuration values in the IdeaBladeConfig object before a component requests them, your custom configuration values will prevail. The timing is critical. DevForce components only read IdeaBladeConfig once at the moment they need a piece of configuration. They stash config away in a place of their convenience (typically a static field) and don't look back. You must update IdeaBladeConfig before a DevForce component reads it; otherwise, the horse has left the barn and your updates are without effect. If you intend to change the IdeaBladeConfig programmatically, do so very early in the client application bootstrapping. The Application_Startup method of your Application class is a good candidate in a WPF or Silverlight app. The Global.asax is a good place for this logic on the server. Two-tier client IdeaBladeConfig example Here's a debugger screenshot of an IdeaBladeConfig in a 2-tier application.
In a 2-tier application, the ObjectServer.ClientSettings.I sDistributed flag is false and remote URL information is irrelevant (the URL information is null in this example). Specifying database connections with configuration Database connection string information is an example of a server side concern. If we want to specify a connection string in code we can do that in one of the IdeaBladeConfig server- oriented configuration settings. The example is taken from a 2-tier IdeaBladeConfig. Because we're focusing on server-side configuration, the following discussion applies as well to a server-side IdeaBladeConfig in an n-tier application. Before explaining how, it's worth noting that database connection strings aren't usually specified in code. They are typically acquired from XML configuration files. In the screenshot, the count of the EdmKeys property is zero. Database connection information is held in EDM Keys. An EDM Key represents connection information about an Entity Data Model data source (e.g., the database). There are no EDM Key specifications in the IdeaBladeConfig. Yet the application works; it retrieves data from the database. Evidently the connection string is coming from somewhere else. It works because DevForce looks for the connection information in a .NET configuration file if you don't specify an EDM Key explicitly. Here's the procedure. When the application queries for an entity, say a Customer entity, DevForce looks up that entity type's DataSourceName. That name is inscribed in the entity class itself as seen in this extract from the generated class file: C# [IbEm.DataSourceKeyName(@"NorthwindEntities")] [IbEm.DefaultEntitySetName(@"NorthwindEntities.Customers")] public partial class Customer : IbEm.Entity { ... VB <IbEm.DataSourceKeyName("NorthwindEntities")> _ <IbEm.DefaultEntitySetName("NorthwindEntities.Customers")> _ Partial Public Class Customer Inherits IbEm.Entity ... Notice that the data source key name is NorthwindEntities. DevForce sees that you did not specify an EDM Key named NorthwindEntities. Therefore it looks for a correspondingly-named <connectionStrings/> element inside the appropriate .NET configuration file: Web.config if the application server runs in IIS; the App.config file in the application assembly if the application is running 2-tier (as in this example). In either place, the <connectionStrings/> element looks something like this:
<connectionStrings> <add name="NorthwindEntities" connectionString="metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.m sl;provider=System.Data.SqlClient;provider connection string="Data Source=localhost;Initial Catalog=NorthwindIB;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" /> </connectionStrings> The unusual looking connectionString value is an Entity Framework connection string. The first sections identify the 3 file parts of the EDM; the last section is the database connection string. <connectionStrings/> is a .NET configuration tag which is why it doesn't appear in the IdeaBladeConfig object; IdeaBladeConfig only holds DevForce-specific configuration data. DevForce now has all it needs to dynamically create an EDM Key with the NorthwindEntities name. You didnt have to specify it explicitly in your configuration. You COULD have done so in which case your explicit definition would take precedence. You can specify the EDM Key in the XML configuration file if you wish. But specifying it in code ... as we do next ... is of greater interest in this IdeaBladeConfig topic. Specifying database connections in code If you're not using a configuration file, perhaps you're getting the connection information from some other external source. Some authors of multi-tenant applications, for example maintain a central, shared database table with different connection strings for each tenant. They can add and remove tenants without modifying configuration files and construct the corresponding EDM Keys on the fly. Most developers would construct such dynamic keys by means of a custom DataSourceKeyResolver. Perhaps you have only one or a few connection string and you truly want to bake those strings directly into your code. There are two simple ways to do it that don't involve a DataSourceKeyResolver. The first and preferable way is to add an EDM Key to the IdeaBladeConfig object. Here is an example that has the same effect as what DevForce did by default, assuming the model and connections shown above: C# var config = IdeaBlade.Core.IdeaBladeConfig.Instance; var keys = config.EdmKeys; keys.Add( new EdmKeyElement { Name = "NorthwindEntities", LogTraceString = false, // whether to log EF generated SQL // Same connection string as above, tweaked for C# Connection = @"metadata= res://DomainModel.Desktop/Northwind.csdl| res://DomainModel.Desktop/Northwind.ssdl| res://DomainModel.Desktop/Northwind.msl; provider=System.Data.SqlClient; provider connection string= 'Data Source=localhost;Initial Catalog=NorthwindIB;Integrated Security=True;MultipleActiveResultSets=True'" } ); VB Dim config = IdeaBlade.Core.IdeaBladeConfig.Instance Dim keys = config.EdmKeys keys.Add(New EdmKeyElement With {.Name = "NorthwindEntities", _ .LogTraceString = False, _ .Connection = "metadata=" & ControlChars.CrLf & "res://DomainModel.Desktop/Northwind.csdl|" _ & ControlChars.CrLf & "res://DomainModel.Desktop/Northwind.ssdl|" & ControlChars.CrLf & _ "res://DomainModel.Desktop/Northwind.msl;" & ControlChars.CrLf & _ "provider=System.Data.SqlClient;" & ControlChars.CrLf & "provider connection string=" _ & ControlChars.CrLf & " 'Data Source=localhost;Initial _ Catalog=NorthwindIB;Integrated Security=True;MultipleActiveResultSets=True'"}) A second way to add the connection string is to add it to the parent configuration's collection of connection strings as in this example: C# var config = IdeaBlade.Core.IdeaBladeConfig.Instance; // Climb up the configuration tree to get to .NET ConnectionStrings var connections = config.Configuration.ConnectionStrings.ConnectionStrings; connections .Add( new ConnectionStringSettings { Name = "NorthwindEntities", // Same connection string as above, tweaked for C# Connection = @"metadata= res://DomainModel.Desktop/Northwind.csdl| res://DomainModel.Desktop/Northwind.ssdl| res://DomainModel.Desktop/Northwind.msl; provider=System.Data.SqlClient; provider connection string= 'Data Source=localhost;Initial Catalog=NorthwindIB;Integrated Security=True;MultipleActiveResultSets=True'" } ); VB Dim config = IdeaBlade.Core.IdeaBladeConfig.Instance ' Climb up the configuration tree to get to .NET ConnectionStrings Dim connections = config.Configuration.ConnectionStrings.ConnectionStrings ' Same connection string as above, tweaked for C# connections.Add(New ConnectionStringSettings With {.Name = "NorthwindEntities", _ .Connection = "metadata=" & ControlChars.CrLf & "res://DomainModel.Desktop/Northwind.csdl|" _ & ControlChars.CrLf & "res://DomainModel.Desktop/Northwind.ssdl|" & ControlChars.CrLf _ & "res://DomainModel.Desktop/Northwind.msl;" & ControlChars.CrLf & _ "provider=System.Data.SqlClient;" & ControlChars.CrLf & "provider connection string=" _ & ControlChars.CrLf & " 'Data Source=localhost;Initial _ Catalog=NorthwindIB;Integrated Security=True;MultipleActiveResultSets=True'"}) This approach is generally less useful as you cannot set the LogTraceString which tells DevForce to log the generated SQL. Adapting Entity Framework connection strings The programmatic string is not an exact duplicate of the one that Entity Framework put in the model project's App.config (or in the Web.config if your model co-habitates with the web application project). You"ll have to make some adjustments: make sure to replace the two """ tokens either with two escaped double quotes (\) or with single quotes (') if your model is in a separate project, replace the ( * ) in res://*/ with the model project assembly's name as in res://DomainModel.Desktop/; if the model project is strongly-named, you'll have to use the strong name of the assembly. Setting the server address dynamically: a Silverlight example Here's a debugger screenshot of an IdeaBladeConfig in a Silverlight client.
Silverlight applications are necessarily n-tier so the ObjectServer.ClientSettings.I sDistributed flag must be true. Notice there is no place to specify a connection string. The EDM Keys node is missing from the Silverlight definition of IdeaBladeConfig. A Silverlight client has no need of EDM Keys and it would be insecure to deploy database connection strings to a remote client; DevForce doesn't let you make this mistake. Other server-only settings such as "UseDCS" are harmless and irrelevant. You can set the server URL information which has 3 parts as you see in the screenshot: Property Example Description RemoteBaseUrl http://localhost Base URL of the server ServerPort 50501 The port, typically 80 (HTTP) or 443 (HTTPS) in production (prefer HTTPS!) ServiceName EntityService.svc The name of the server-side service; "EntityService.svc" is the default value and should rarely change. DevForce combines the parts to form this: http://localhost:5051/EntityServerice.svc. You are free to devise your own runtime strategy and policies for constructing an alternative remote server URL. For example, in a developer build, you might display a ComboBox at launch so the developer/user could pick an appropriate server for that particular launch: a test server, a stage server, or the production server. A full .NET n-tier client, written in WPF or Windows Forms or even a Console application, could do the same. Take offline Last modified on May 04, 2011 16:05 DevForces client-side cache enables applications to operate in a partially connected or fully disconnected environment. You can query, modify, and even create new entities while offline, and you can save the cache to the file system to preserve the state of the entities if the application or computer needs to be shut down. Query entities offline With a few small exceptions, the same LINQ queries you execute while connected will work disconnected, except that they will be fulfilled entirely from the client-side cache. While disconnected, the EntityManager will use a QueryStrategy of CacheOnly for all queries since the EntityServer is not available. (To learn more about QueryStrategies, see Control query execution.) Create, modify, and delete entities offline Creating and modifying entities is not affected by being disconnected, as these are purely client-side operations until they are committed to the server by SaveChanges or SaveChangesAsync. Similarly, you can mark the deletion of an entity while disconnected, and the EntityState will change to Deleted, but the entity will not be committed until the save. Connect and Disconnect You can tell the EntityManager to enter the disconnected state by calling the Disconnect method. When network access is restored, you can reconnect by calling Connect or ConnectAsync. While disconnected, the IsConnected property will return false. If the EntityManager is in the connected state and it experiences a loss of connection, it will: 1. Enter into the disconnected state. 2. Raise the EntityServerError event with an EntityServerConnectionException in the EntityServerErrorEventArgs, which you should handle and indicate to the user. 3. Throw an EntityServerConnectionException if the EntityServerErrorEventArgs was not marked as handled in the EntityServerError event. When creating a new EntityManager while offline, you should pass false for the shouldConnect parameter. This will keep the EntityManager from automatically attempting to establish a connection to the entityserver. Prepare to go offline Last modified on March 22, 2011 16:18 When you prepare to go offline, you will need to populate the client-side cache with whatever entities you will need before the database becomes unavailable. To do this, simply query for the entities that you need and they will automatically be brought into the cache. The Include operator in LINQ is very handy for this, as it allows you to fetch related entities in a single query. For example, if you are caching all of today's Orders, you might also want to fetch the Customers, OrderDetails, and Products for those orders at the same time: C# private void CacheTodaysOrders(EntityManager manager) { var query = manager.Orders.Where(o => o.OrderDate == DateTime.Today) .Include("Customer") .Include("OrderDetails.Products");
query.ExecuteAsync(CacheTodaysOrdersComplete); }
private void CacheTodaysOrdersComplete(EntityQueryOperation<Order> args) { } VB Private Sub CacheTodaysOrders(ByVal manager As EntityManager) Dim query = manager.Orders.Where(Function(o) o.OrderDate = Date.Today) _ .Include("Customer") _ .Include("OrderDetails.Products")
query.ExecuteAsync(AddressOf CacheTodaysOrdersComplete) End Sub
Private Sub CacheTodaysOrdersComplete(ByVal args As EntityQueryOperation(Of Order)) End Sub If you are caching entities asynchronously, remember to wait for the operation to finish before going offline! (To learn more about fetching related entities in the same query, see Include related entities.) There is no explicit limit to how much you can cache, but eventually memory, serialization, and query performance may be limiting factors. For large datasets (>50MB), you might consider using a local database such as SQL Server Express or SQL Server Compact Edition. (Note that you can create an EntityManager that connects to a local database by passing an EntityServiceOption of UseLocalService in the constructor.) Save cache locally Last modified on April 06, 2011 12:43 Contents Save and restore Sample save/restore using Isolated Storage Save custom properties Other technical considerations The EntityManager can serialize or save its cache to a stream or file.
You may want to save cached entities when: The application must be able to run offline for extended periods, and you need to preserve the state of the cache if the application or computer is shut down. The user may accumulate many changes over a long time and you need to backup their data. This may also occur because the user is not yet ready to commit their changes to the datastore, or because the modifications to the entities are incomplete, and they might not pass validation checks required for saving. Often, especially in Silverlight applications, isolated storage is used to hold the saved data, but any Stream can be used. For WinClient applications, saving directly to a file is also available. Saving to a file is not supported in Silverlight applications. Save and restore To access the EntityManager's cache, use its CacheStateManager property. The CacheStateManager class contains the methods SaveCacheState and RestoreCacheState which are used to save and load the cache. Using SaveCacheState, you can save to a stream or file either the entire EntityManager cache or only a subset of entities. With RestoreCacheState, you can restore a previously saved cache state into an EntityManager. When restoring you can also determine the RestoreStrategy to use. The RestoreStrategy determines how the entities are merged if they already exist in the EntityManager's cache and whether other default settings on the EntityManager should be restored. If you are restoring the cache into an empty EntityManager, then you don't need to worry about merge conflicts. This only occurs in advanced scenarios where you want to merge the incoming cache into an already active EntityManager that may have changes you want to preserve. (For example, you are importing another user's cache, or you are restoring a backup cache, but want to keep the current modifications.) In most cases, a RestoreStrategy of Normal is used. This preserves all of the changes in the EntityManager so that they take precedence in the event of a conflict with the cache being imported. However, you can configure your own RestoreStrategy if needed. For an in depth discussion about merging entities see Merge query results into the entity cache. Both SaveCacheState and RestoreCacheState also allow you to save/restore data in text format. Binary format is used by default, but text format can be useful when diagnosing problems or for situations when the serialized data will be used by non-DevForce applications. Sample save/restore using Isolated Storage C# private void SaveCacheToIsolatedStorage(EntityManager manager, String fileName) { // Remember to add error handling! using (var isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { using (var isoStream = new IsolatedStorageFileStream(fileName, FileMode.Create, isoFile)) { manager.CacheStateManager.SaveCacheState(isoStream); } } }
private void RestoreCacheFromIsolatedStorage(EntityManager manager, String fileName) { // Remember to add error handling! using (var isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { using (var isoStream = new IsolatedStorageFileStream(fileName, FileMode.Open, isoFile)) { manager.CacheStateManager.RestoreCacheState(isoStream, RestoreStrategy.Normal); } } } VB Private Sub SaveCacheToIsolatedStorage(ByVal manager As _ EntityManager, ByVal fileName As String) ' Remember to add error handling! Using isoFile = IsolatedStorageFile.GetUserStoreForApplication() Using isoStream = New IsolatedStorageFileStream(fileName, FileMode.Create, isoFile) manager.CacheStateManager.SaveCacheState(isoStream) End Using End Using End Sub
Private Sub RestoreCacheFromIsolatedStorage(ByVal manager As _ EntityManager, ByVal fileName As String) ' Remember to add error handling! Using isoFile = IsolatedStorageFile.GetUserStoreForApplication() Using isoStream = New IsolatedStorageFileStream(fileName, FileMode.Open, isoFile) manager.CacheStateManager.RestoreCacheState(isoStream, RestoreStrategy.Normal) End Using End Using End Sub Save custom properties If you have defined custom properties on your entity (properties that are not backed by the datastore and are not calculated) and want these serialized along with the entity, you should mark them with the DataMember attribute and implement a public get and set for the property. (Remember to include a reference to the System.Runtime.Serialization assembly.) C# [DataMember] public String Note { get; set; } VB <DataMember> Public Property Note() As String In addition, by marking the property with the DataMember attribute, the value will also serialize between the client and EntityServer, so if you set it in one location, it will be available in the other. You can also serialize complex types. Other technical considerations Note that the temporary id map is also serialized to the stream. This allows you to create new entities without the server (even when connected), and for id fixup to be performed on the entity graph when the entities are committed to the database. See Custom id generation to learn more. When restoring the entity cache, the query cache is also cleared. See Cache a query to learn more. The techniques described here can also be used to support both design time data and testing. Secure offline data Last modified on March 22, 2011 16:19 If you are working with sensitive data, you will want to make sure to secure the offline data in case the client computer is compromised. Encrypt the cache You should use a CryptoStream to encrypt the cache before saving it to the file system. To prevent discovery of the encryption key, use a secure hash of the user's credentials (e.g. username and password) to derive the encryption key and never store the key or password on the file system where it can be discovered. This way, the serialized cache can only be decrypted by someone with the correct credentials. To learn more about encryption, see the SymmetricAlgorithm class. Authenticate while offline When offline, there is no host to authenticate the user, and you cannot call the Login or LoginAsync method. This is ok, since there is no access to the database or other non-local datasource in this circumstance. To authenticate the user, you can: 1. Check for the successful decryption of the cache which will indicate that they have entered the correct credentials, or 2. If you are not encrypting the cache, compare the hash of the credentials to the locally stored hash that was computed from a prior successful login.
Extend Last modified on March 08, 2011 13:58 DevForce provides a number of extensibility points - interfaces, base types, and attributes - which allow the developer to specify and customize behaviors. For example, the application developer can customize login credentials and authentication processing; intercept the query and save pipelines; control logging; and customize communication configuration; all through extensibility points within DevForce. In the following topics we'll discuss what's available and how to control discovery. Customize Last modified on March 17, 2011 18:16 Contents Use DevForce Types Use DevForce attributes There are a number of extensibility points within DevForce which allow you to customize your application processing. We list them here.
The first time DevForce finds and uses your custom classes it will log this fact to the debug log (or other ITraceLogger implementation). This information, along with logging about all assemblies loaded into the "parts" catalog, can help in diagnosing problems should they occur. Use DevForce Types The following tables list the DevForce interfaces and base types which you can implement or extend to provide customization for your application. You'll notice that in some cases there are default implementations for an interface; in these cases you can either implement the interface directly or sub-type the base class to provide the customization you require. Interface Discovery Purpose Topic IAuthenticationManager Client Provides credentials to an EntityManager. Security IAuthenticationProvider Client Provides an IAuthenticationManager. Security ICompositionContextResolver Client and Server Provides for resolution of custom Composition Contexts. Discovery IConcurrencyStrategy Server Assigns the value of a concurrency field. Concurrency IDataSourceKeyResolver Client and Server Determines the data source key and connection information. Data sources IEntityLoginManager Server Validates user credentials. Security IIdGenerator Client and Server Assigns temporary and permanent IDs for entities. Custom ids IKnownType Client and Server Indicates a known type. Known types IKnownTypeProvider Client and Server Provides programmatic specification of known types. Known types ILoginCredential Client and Defines user credentials. Security Server ITraceLogger Client and Server A custom logger. Log ITraceLoggerProvider Client and Server Provides custom logging. Log IVerifierProvider Client and Server Provides programmatic specification of verifiers. Validation Base Type Discovery Purpose Topic AspAuthenticatingLoginManager Server Default IEntityLoginManager when using ASP.NET security. Security BaseCompositionContextResolver Client and Server Default ICompositionContextResolver implementation. Discovery DefaultConcurrencyValueSetter Server Default IConcurrencyStrategy implementation. Concurrency DefaultDataSourceKeyResolver Client and Server Default IDataSourceKeyResolver implementation. Data sources EntityServerErrorInterceptor Server Intercept and filter exceptions before sending to the client. Security EntityServerPocoSaveAdapter Server Provides save processing for POCO classes. POCO EntityServerQueryInterceptor Server Intercept and customize query processing. Query EntityServerSaveInterceptor Server Intercept and customize save processing. Save EntityServiceApplication Server Allows for interception of EntityService events. Configure FormsAuthenticationLoginCredential Client and Server ILoginCredential which can be used with ASP.NET Security. Security LoginCredential Client and Server Default ILoginCredential implementation. Security ServiceHostEvents Server Customize configuration of WCF services. Configuration ServiceProxyEvents Client Customize configuration of WCF proxies. Configuration UserBase Client and Server Default IPrincipal implementation. Security UserIdentity Client and Server Default IIdentity implementation. Security Use DevForce attributes There are also a few attributes which DevForce will look for to provide specific functionality. There are other DevForce attributes, such as those for validation, property interception, and authorization, but we won't list them here since they don't directly relate to the topic of discoverability and extensibility. Attribute Discovery Purpose Topic AllowRpcAttribute Server Indicates an invoked server method. Server methods DiscoverableTypeAttribute Client and Server Indicates a known type. Known types EnableClientAccessAttribute Server Indicates a provider of CRUD processing for POCO classes. POCO Discovery Last modified on April 18, 2011 18:58 Contents Startup Controlling discovery What should be discovered Discovery, sometimes called probing, is the process DevForce uses to find your entity models and custom components.
Startup DevForce uses the Managed Extensibility Framework (MEF) from Microsoft to provide much of its runtime extensibility. When your application starts, DevForce builds a "parts" catalog on both client and server containing the customizations it has discovered. By default, DevForce will check all non-system / non-IdeaBlade assemblies for "exports", i.e.,implementations of the DevForce interfaces and base types, as well as other components or your application. In Silverlight applications, discovery is initially done using the assembly parts in the main XAP. For modular applications with on-demand download of XAPs DevForce also supports on- demand discovery. In Desktop, ASP.NET applications and the EntityServer, discovery uses the assemblies in the main executable/bin folder. The CompositionHost manages the discovery process, and provides properties and methods you can use to modify the default behavior, access the underlying MEF container, and add dynamic content. Controlling discovery In a large application with many assemblies you may find that the discovery performed at startup takes too long, or maybe you decide there's just no reason to look in assemblies which will never contain exports or entity models. Trace messages about the assemblies added the the "parts" catalog, all probe assemblies found, and total discovery time are written to the debug log and can help diagnose a problem. The easiest and most common way to alter the default discovery behavior is to change the search and/or ignore pattern(s) DevForce will use. For ASP.NET and Silverlight applications the default SearchPattern is "*.dll"; for other applications the default is "*.dll" and "*.exe". For all application types, the default IgnorePatterns are set to exclude IdeaBlade and system assemblies, as well as assemblies from many third party control suites. Both SearchPatterns and IgnorePatterns accept either simple wildcards or Regex patterns. Simple wildcard searches are converted into Regex patterns by DevForce when probing occurs. The search is case insensitive. When should you use SearchPatterns versus IgnorePatterns? SearchPatterns are useful when you know specifically what you're looking for; while IgnorePatterns are helpful when you know what you don't want. Here's a sample using the SearchPatterns to limit probing to only a single assembly. Generally in larger applications you'll have multiple assemblies to be probed, so you should be sure to include one or more patterns to cover them. This first clears the default SearchPatterns, and then sets a pattern for a specific assembly named "MyDomainModel.dll". C# IdeaBlade.Core.Composition.CompositionHost.SearchPatterns.Clear(); IdeaBlade.Core.Composition.CompositionHost.SearchPatterns.Add("MyDomainModel.dll"); VB IdeaBlade.Core.Composition.CompositionHost.SearchPatterns.Clear() IdeaBlade.Core.Composition.CompositionHost.SearchPatterns.Add("MyDomainModel.dll") Now let's try modifying the IgnorePatterns. DevForce will by default ignore the following: "IdeaBlade.*", "System.*", "Microsoft.*", "*.vshost.*", "mscorlib.*", "WindowsBase.*", "PresentationCore.*", "PresentationFramework.*", "Intersoft.*", "Telerik.*", "DevExpress.*", "Infragistics*", "WPFToolkit.*", "Syncfusion.*", "Janus.*", "C1.*", and "PerpetuumSoft.*". You shouldn't clear this list, and you certainly shouldn't remove the "IdeaBlade.*" assemblies, since both actions will cause your application to fail, but you can add additional third party tools and suites to the list. For example, here we're adding an exclusion for all assemblies (and other files) matching "GalaSoft.*". C# IdeaBlade.Core.Composition.CompositionHost.IgnorePatterns.Add("GalaSoft.*"); VB IdeaBlade.Core.Composition.CompositionHost.IgnorePatterns.Add("GalaSoft.*") You need to set the SearchPatterns and IgnorePatterns as early as possible in your application, since as soon as you use the IdeaBladeConfig.Instance, TraceFns and DebugFns methods, or an EntityManager, initialization will automatically occur. In an EntityServer hosted by IIS or an ASP.NET application, the best place to set the patterns is in the application startup logic in the global.asax. In Silverlight, you should do this in the Application_Startup logic to ensure that your changes are made before probing occurs. In a desktop application, set the patterns before you begin using an EntityManager or other DevForce components. If you've set SearchPatterns and/or IgnorePatterns, it's usually a good idea to check the debug log for the tracing messages written about what DevForce has discovered. It's easy to mistype a pattern and not have your assemblies probed, or alternately to have too many assemblies probed resulting in poor startup performance. The first time DevForce finds and uses your custom classes it will log this fact to the debug log (or other ITraceLogger implementation). This information, along with logging about all assemblies loaded into the "parts" catalog, can help in diagnosing problems should they occur. What should be discovered There are several components of your application which DevForce must find in order for your application to work as wanted. All custom implementations of DevForce interfaces and base types. These are the exports placed in the parts catalog. Entity models (aka domain models) - DevForce must be able to find any assembly holding a model. It uses this assembly to load model metadata and gather other information about the types in your model. Remote server methods - If your application is using either remote server methods or "Push" methods, then DevForce must be able to find them. POCO providers and types - For DevForce to find your POCO types and providers, they must be defined in an assembly probed. Known types - For any of the types which can be transmitted across tiers - for example those types passed to or from a remote server method - DevForce must know these types before initiating communications. In all the above cases, DevForce will look through all the assemblies which have been probed to find what it needs. If you've chosen to modify either the SearchPatterns or IgnorePatterns, make sure that the patterns will allow all assemblies needed to be discovered. You can check the read only CompositionHost.Instance.ProbeAssemblies at runtime to see which assemblies were found; they're also listed in the debug log. On-demand discovery Large Silverlight applications can be broken into smaller modules, downloaded "on demand". Visual Studio tooling allows the developer to create separate deployment packages which can be dynamically downloaded as needed. To use DevForce within a downloaded module you must register it for on-demand discovery. This feature is available only in Silverlight.
Overview On-demand discovery allows DevForce to probe your new content for any entity models, known types, or custom implementations of DevForce types. You can ask DevForce to download the content, or once downloaded you can tell DevForce about it. Adding dynamic content for on-demand discovery causes DevForce to recompose its "parts" catalog and metadata about your models and components. If you have dynamic content which you neglect to tell DevForce about, you'll find both that your customizations are not found and exceptions occur when using the entity model. (The most likely error is a message that the entity type queried is not a "known type".) You can register new modules at any time, and start using your entity model and other DevForce features within it as soon as discovery completes. The CompositionHost will fire a Recomposed event once ready, which you can listen on to check for completion or if any errors occurred. To perform on-demand discovery, use one of the Add overloads. Trace messages are also generated, which you can capture using a custom logger or trace viewer. It's common practice to use Application Library Caching (ALC) for XAPs in a multi-module application. The DevForce assemblies all support ALC. With Prism modularity If you're using Prism modularity you can tell DevForce about the new content, either once the module downloads or when it initializes. Here's a sample which adds the Uri of the downloaded module to the CompositionHost. C# public void Download(string moduleName) { this.ModuleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted; this.ModuleManager.LoadModule(moduleName); } private void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e) { var uri = new Uri(e.ModuleInfo.Ref, UriKind.Relative); IdeaBlade.Core.Composition.CompositionHost.Add(uri); } VB Public Sub Download(ByVal moduleName As String) AddHandler Me.ModuleManager.LoadModuleCompleted, _ AddressOf ModuleManager_LoadModuleCompleted Me.ModuleManager.LoadModule(moduleName) End Sub Private Sub ModuleManager_LoadModuleCompleted(ByVal sender As Object, _ ByVal e As LoadModuleCompletedEventArgs) Dim uri = New Uri(e.ModuleInfo.Ref, UriKind.Relative) IdeaBlade.Core.Composition.CompositionHost.Add(uri) End Sub See our sample DevForce/Prism Modularity Quickstart for more information. With MEF deployment catalog If you're using the MEF DeploymentCatalog, once the catalog downloads you can tell DevForce that new content is available by adding the DeploymentCatalog to the CompositionHost. For example: C# public void AddXap(string uri) { DeploymentCatalog catalog; catalog = new DeploymentCatalog(uri); catalog.DownloadAsync(); catalog.DownloadCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(catalog_DownloadComplet ed) }
private void catalog_DownloadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { IdeaBlade.Core.Composition.CompositionHost.Add(sender as DeploymentCatalog); } V B Public Sub AddXap(ByVal uri As String) Dim catalog As DeploymentCatalog catalog = New DeploymentCatalog(uri) catalog.DownloadAsync() AddHandler catalog.DownloadCompleted, AddressOf catalog_DownloadCompleted End Sub
Private Sub catalog_DownloadCompleted(ByVal sender As Object, _ ByVal e As System.ComponentModel.AsyncCompletedEventArgs) IdeaBlade.Core.Composition.CompositionHost.Add(TryCast(sender, _ DeploymentCatalog)) End Sub If using the DeploymentCatalogService shown in MEF samples, you likely already have code for the catalog_DownloadCompleted event handler. Add the line to call CompositionHost.Add for the catalog. With a DynamicXap If you're not using MEF or Prism there's still another means of notifying DevForce about dynamic content with the DynamicXap class. The class can be constructed in a few different ways, but the easiest may be with a Uri, to have DevForce automatically download the content. C# public void DoDownload(string xapName) { var xap = new IdeaBlade.Core.DynamicXap(new Uri(xapName, UriKind.Relative)); IdeaBlade.Core.Composition.CompositionHost.Add(xap); } VB Public Sub DoDownload(ByVal xapName As String) Dim xap = New IdeaBlade.Core.DynamicXap(New Uri(xapName, UriKind.Relative)) IdeaBlade.Core.Composition.CompositionHost.Add(xap) End Sub Listening for DynamicXap load You can load a DynamicXap independently of adding it to the CompositionHost. Listen on its Loaded event to tell you when the download has completed or if an error occurred. C# var xap = new DynamicXap(new Uri("SecondModule.xap", UriKind.Relative)); xap.Loaded += (o, args) => { if (args.HasError) { TraceFns.WriteLine(args.Error.Message); } else { var dzap = o as DynamicXap; TraceFns.WriteLine(dzap.Name + " downloaded"); } }; VB Dim xap = New DynamicXap(New Uri("SecondModule.xap", UriKind.Relative)) AddHandler xap.Loaded, Sub(o, args) If args.HasError Then TraceFns.WriteLine(args.Error.Message) Else Dim dzap = TryCast(o, DynamicXap) TraceFns.WriteLine(dzap.Name & " downloaded") End If End Sub Listening for recomposition events Regardless of how you've added content to the CompositionHost, you can listen for its Recomposed event to notify you when new content has been downloaded. C# CompositionHost.Recomposed += CompositionHost_Recomposed; VB AddHandler CompositionHost.Recomposed, AddressOf CompositionHost_Recomposed The RecomposedEventArgs will indicate the Xap or assembly triggering the recomposition, or the error if an exception was thrown. You can use the Name property on the Xap to identify the on-demand module. Discovery by context You can gain finer-grained control over the discovery process by using type and metadata filters with a composition context.
The CompositionContext You may have noticed a CompositionContext argument can be passed into the EntityManager constructor, and the probe messages in your log all mention it too:
Just what is this, and why does it keep showing up everywhere? In the discovery topic we discussed how DevForce uses MEF to discover the extensible components of your application, but what we didn't mention is that this discovery is contextual. The default context is just that, a default, and always used when you haven't specified a context. The CompositionContext.Default provides no special type or metadata filtering. When and why to use a CompositionContext? The CompositionContext is used in DevForce's built-in support for faking, but it's also useful for creating mocks during testing, and in any situation in which you might want several different custom implementations of an interface or base class. For example, you might have a requirement that in most cases you'll use an EntityServerQueryInterceptor in a certain way, but in particular situations you'd like different interceptor logic. With a custom CompositionContext you can easily define that "particular situation", and define a custom EntityServerQueryInterceptor to be used only for it. Most, but not all, of the DevForce extensible components support a custom context. Those that do not are ones needed during application initialization and not as part of EntityManager activities. For example, custom loggers and components controlling service or proxy configuration cannot be discovered or composed contextually. You can define and use any number of CompositionContexts within your application. As we'll see below it's generally the EntityManager, via its constructor, which determines the context in use. Creating a custom CompositionContext You can implement the ICompositionContextResolver interface or extend the BaseCompositionContextResolver to define and resolve your custom CompositionContexts. The BaseCompositionContextResolver will automatically register any statically defined composition contexts found within the sub-class, but you can also override its GetCompositionContext method to programmatically define new contexts. Here's a sample resolver defining two custom contexts, "MockQuery" and "Admin": C# public class MyCompositionContextResolver : BaseCompositionContextResolver {
public static CompositionContext MockQuery = CompositionContext.Fake .WithGenerator(typeof(MockEntityServerQueryInterceptor)) .WithName("MockQuery");
public static CompositionContext Admin = CompositionContext.Default .WithGenerator(typeof(AdminEntityServerSaveInterceptor)) .WithGenerator(typeof(AdminEntityServerQueryInterceptor)) .WithName("Admin"); } VB Public Class MyCompositionContextResolver Inherits BaseCompositionContextResolver
Public Shared MockQuery As CompositionContext = _ CompositionContext.Fake. _ WithGenerator(GetType(MockEntityServerQueryInterceptor)). _ WithName("MockQuery")
Public Shared Admin As CompositionContext = _ CompositionContext.Default. _ WithGenerator(GetType(AdminEntityServerSaveInterceptor)). _ WithGenerator(GetType(AdminEntityServerQueryInterceptor)). _ WithName("Admin") End Class We also see above that these custom contexts are defined from existing built-in contexts. Once created a CompositionContext is immutable, but you can easily create new contexts based on an existing context and supply different composition criteria. Above we see a new context based on the Fake context but using its own custom EntityServerQueryInterceptor, and another context based on the Default context but using custom query and save interceptors. The sample above is also defining the extensible types specific to the context (although we didn't show the code for them). In defining a custom composition context you must both register the context and define the extensible components it will use. There are two mechanisms used to accomplish this: one allows you to specify the types to be used when you create the context, while the other allows you to define metadata filters to be applied during discovery. Discovery with types specified This approach is the one you'll see most often in discovery examples, and was also used in the sample above. Here you specify the generators or the types to be used by the custom context. Remember to do two things: 1. Use the WithGenerator method to register types to the context. 2. Mark these types with the System.ComponentModel.Composition.PartNotDiscoverable attribute. This is required to hide the types from standard MEF discovery, since here DevForce is preempting that discovery. Here's a sample resolver defining the composition contexts. Instead of defining static fields we're overriding the GetCompositionContext method to define the contexts at run time. C# public class ContextResolver : BaseCompositionContextResolver {
public override CompositionContext GetCompositionContext(string compositionContextName) {
Public Overrides Function GetCompositionContext(ByVal compositionContextName As String) As CompositionContext
If compositionContextName = "Test" Then Return CompositionContext.Default.WithGenerator(New Type() {GetType(TestLoginManager), GetType(TestQueryInterceptor)}).WithName("Test") ElseIf (compositionContextName = "Dev") Then Return CompositionContext.Default.WithGenerator(New Type() { GetType(DevLoginManager)}).WithName("Dev"); } Else Return MyBase.GetCompositionContext(compositionContextName) End If End Function End Class Remember that your resolver may need to be defined on both client and server, depending on the type generators it defines and where they will be used. For example, an IEntityLoginManager is used only on the server. Be sure to decorate the types used by your custom contexts with the MEF PartNotDiscoverable attribute. This tells both MEF and DevForce to ignore the types during standard discovery. Below are a few very simple samples of custom types for the contexts defined above. C# [PartNotDiscoverable] public class DevLoginManager : IEntityLoginManager {
public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) { return new UserBase(new UserIdentity("Dev", "Custom", true), new string[] { "Dev" }); }
public void Logout(IPrincipal principal, EntityManager entityManager) { } }
[PartNotDiscoverable] public class TestLoginManager : IEntityLoginManager {
public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) { return new UserBase(new UserIdentity("Test", "Custom", true), new string[] { "Test" }); }
public void Logout(IPrincipal principal, EntityManager entityManager) { } }
[PartNotDiscoverable] public class TestQueryInterceptor : EntityServerQueryInterceptor { protected override bool ExecuteQuery() { TraceFns.WriteLine("User " + this.Principal.Identity.Name + " is executing query " + this.Query.ToString()); return base.ExecuteQuery(); } } VB <PartNotDiscoverable> Public Class DevLoginManager Implements IEntityLoginManager
Public Function Login(ByVal credential As ILoginCredential, ByVal entityManager As EntityManager) As IPrincipal Return New UserBase(New UserIdentity("Dev", "Custom", True), New String() { "Dev" }) End Function
Public Sub Logout(ByVal principal As IPrincipal, ByVal entityManager As EntityManager) End Sub End Class
<PartNotDiscoverable> Public Class TestLoginManager Implements IEntityLoginManager
Public Function Login(ByVal credential As ILoginCredential, ByVal entityManager As EntityManager) As IPrincipal Return New UserBase(New UserIdentity("Test", "Custom", True), New String() { "Test" }) End Function
Public Sub Logout(ByVal principal As IPrincipal, ByVal entityManager As EntityManager) End Sub End Class
<PartNotDiscoverable> Public Class TestQueryInterceptor Inherits EntityServerQueryInterceptor Protected Overrides Function ExecuteQuery() As Boolean TraceFns.WriteLine("User " & Me.Principal.Identity.Name & " is executing query " & Me.Query.ToString()) Return MyBase.ExecuteQuery() End Function End Class Discovery with metadata filters Instead of defining the types to be used with the custom context when you create the context you can instead define metadata filtering rules. Here you'll do the following: 1. Build one or more metadata filters with BuildExportFilter. 2. Registers the filters with the context using WithFilter and WithTypeFilter methods. 3. Mark your types with the System.ComponentModel.Composition.ExportMetadata and System.ComponentModel.Composition.InheritedExport attributes. Here's a sample resolver defining the composition contexts. As above we overrode the GetCompositionContext method to define the contexts at run time. The BuildExportFilter method is used to build a filter for the metadata attributes to be used. Here we'll be using only a single metadata attribute named "Env". We're also using the WithFilter method to indicate that any types meeting the metadata criteria will be used. We could also use the WithTypeFilter to indicate specific metadata by type. C# public class ContextResolver : BaseCompositionContextResolver {
public override CompositionContext GetCompositionContext(string compositionContextName) {
if (compositionContextName == "Test") { var filter = CompositionContext.BuildExportFilter("Env", "Test"); return CompositionContext.Default .WithFilter(filter) .WithName("Test"); } else if (compositionContextName == "Dev") { var filter = CompositionContext.BuildExportFilter("Env", "Dev"); return CompositionContext.Default .WithFilter(filter) .WithName("Dev"); } else return base.GetCompositionContext(compositionContextName); } } VB Public Class ContextResolver Inherits BaseCompositionContextResolver
Public Overrides Function GetCompositionContext(ByVal compositionContextName As String) As CompositionContext
If compositionContextName = "Test" Then Dim filter = CompositionContext.BuildExportFilter("Env", "Test") Return CompositionContext.Default.WithFilter(filter).WithName("Test") ElseIf compositionContextName = "Dev" Then Dim filter = CompositionContext.BuildExportFilter("Env", "Dev") Return CompositionContext.Default.WithFilter(filter).WithName("Dev") Else Return MyBase.GetCompositionContext(compositionContextName) End If End Function End Class We must decorate our types to add the metadata wanted. This requires both the ExportMetadata attribute, to add metadata to the type, and the InheritedExport attribute, which is required by MEF to ensure that the custom metadata is found. Here are the same simple classes as defined above, but this time using metadata. C# [InheritedExport(typeof(IEntityLoginManager))] [ExportMetadata("Env", "Dev")] public class DevLoginManager : IEntityLoginManager {
public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) { return new UserBase(new UserIdentity("Dev", "Custom", true), new string[] { "Dev" }); }
public void Logout(IPrincipal principal, EntityManager entityManager) { } }
[InheritedExport(typeof(IEntityLoginManager))] [ExportMetadata("Env", "Test")] public class TestLoginManager : IEntityLoginManager {
public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) { return new UserBase(new UserIdentity("Test", "Custom", true), new string[] { "Test" }); }
public void Logout(IPrincipal principal, EntityManager entityManager) { } }
[InheritedExport(typeof(EntityServerQueryInterceptor))] [ExportMetadata("Env", "Test")] public class TestQueryInterceptor : EntityServerQueryInterceptor {
protected override bool ExecuteQuery() { TraceFns.WriteLine("User " + this.Principal.Identity.Name + " is executing query " + this.Query.ToString()); return base.ExecuteQuery(); } } VB <InheritedExport(GetType(IEntityLoginManager)), ExportMetadata("Env", "Dev")> Public Class DevLoginManager Implements IEntityLoginManager
Public Function Login(ByVal credential As ILoginCredential, ByVal entityManager As EntityManager) As IPrincipal Return New UserBase(New UserIdentity("Dev", "Custom", True), New String() { "Dev" }) End Function
Public Sub Logout(ByVal principal As IPrincipal, ByVal entityManager As EntityManager) End Sub End Class
<InheritedExport(GetType(IEntityLoginManager)), ExportMetadata("Env", "Test")> Public Class TestLoginManager Implements IEntityLoginManager
Public Function Login(ByVal credential As ILoginCredential, _ ByVal entityManager As EntityManager) As IPrincipal Return New UserBase(New UserIdentity("Test", "Custom", True), New String() { "Test" }) End Function
Public Sub Logout(ByVal principal As IPrincipal, ByVal entityManager As EntityManager) End Sub End Class
<InheritedExport(GetType(EntityServerQueryInterceptor)), ExportMetadata("Env", "Test")> Public Class TestQueryInterceptor Inherits EntityServerQueryInterceptor
Protected Overrides Function ExecuteQuery() As Boolean TraceFns.WriteLine("User " & Me.Principal.Identity.Name & " is executing query " & _ Me.Query.ToString()) Return MyBase.ExecuteQuery() End Function End Class Using a CompositionContext The CompositionContext is usually specified by name during the construction of an EntityManager: C# var em = new EntityManager(compositionContextName: MyCompositionContextResolver.MockQuery.Name); VB Dim em As New EntityManager(compositionContextName:=MyCompositionContextResolver.MockQuery.Name) We saw earlier that every custom CompositionContext was also given a unique name, via the WithName method. It's this name which is provided in the EntityManager constructor. The name defines the context to be used for all composition performed during the login, query, save, and other operations performed by the EntityManager, and the EntityServer it's connected to. In a few types, such as the VerifierEngine, the CompositionContext may also be set explicitly. This is because these classes may be instantiated and used independently of any EntityManager. If the CompositionContext name isn't provided to an EntityManager the CompositionContext.Default will be used. Custom fakes and mocks Custom composition contexts are particularly useful in creating custom fakes and mocks for testing. The built-in support for faking is discussed separately in more detail, but here we show how to create custom contexts to aid in testing. Let's assume we want to test how well our client code copes with a query exception thrown on the server. You generally don't want to force a query exception by modifying your production code, but you can test this easily with a mock. C# public class TestCompositionContextResolver : BaseCompositionContextResolver { ///<summary> /// A version of the DevForce FakeCompositionContext /// extended by a custom EntityServerQueryInterceptor ///</summary> public static CompositionContext FailingQueryContext = CompositionContext.Fake .WithGenerator(typeof(FailingEntityServerQueryInterceptor)); }
///<summary> /// An EntityServerQueryInterceptor that throws an /// exception no matter what the query. ///</summary> [PartNotDiscoverable] public class FailingEntityServerQueryInterceptor : EntityServerQueryInterceptor { protected override bool ExecuteQuery() { throw new InvalidOperationException(); } } VB Public Class TestCompositionContextResolver Inherits BaseCompositionContextResolver '''<summary> ''' A version of the DevForce FakeCompositionContext ''' extended by a custom EntityServerQueryInterceptor '''</summary> Public Shared FailingQueryContext As CompositionContext = _ CompositionContext.Fake.WithGenerator(GetType(FailingEntityServerQueryInterceptor)) End Class
'''<summary> ''' An EntityServerQueryInterceptor that throws an ''' exception no matter what the query '''</summary> <PartNotDiscoverable()> _ Public Class FailingEntityServerQueryInterceptor Inherits EntityServerQueryInterceptor Protected Overrides Function ExecuteQuery() As Boolean Throw New InvalidOperationException() End Function End Class Here we've written a FailingEntityServerQueryInterceptor that always throws an exception no matter what the query. Note the PartNotDiscoverable attribute on the custom class. This tells MEF to skip this class when discovering EntityServerQueryInterceptors. We don't want this one popping up in our production application! Now that we've created a custom context that incorporates our interceptor, let's see how it affects a client-side query. C# [TestMethod] public void MockQueryFail() {
var em = new DomainModelEntityManager(compositionContextName: TestCompositionContextResolver.FailingQueryContext.Name);
var query = em.Customers.Where(c => c.CompanyName.StartsWith("S")); query.ToList(); // Yep, this fails, better think about some try/catch logic. } VB <TestMethod> _ Public Sub MockQueryFail()
Dim em As New DomainModelEntityManager(compositionContextName := TestCompositionContextResolver.FailingQueryContext.Name)
Dim query = em.Customers.Where(Function(c) c.CompanyName.StartsWith("S")) query.ToList() ' Yep, this fails, better think about some try/catch logic. End Sub
Faking Last modified on April 28, 2011 15:47 Contents Faking with CompositionContext.Fake Simple Faking Working directly with a fake backing store o Local vs. remote stores o Store methods Troubleshooting Additional resources We've seen how DevForce supports discovery by context, something particularly useful in testing. DevForce also contains built-in support for faking using this facility. There are a few definitions of faking, but let's use one from Martin Fowler in his post "Mocks Aren't Stubs": "Fakeobjects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example)."
Faking with CompositionContext.Fake CompositionContext.Fake is a composition context in which "fake" implementations of several important DevForce interfaces are provided to simulate end-to-end query and save operations against a real backing store. Faking is also useful in facilitating model-first development, and providing data and functionality for demo applications. CompositionContext.Fake components include: 1. Id generation with the FakeIdGenerator to allow both temporary ids for new entities and translation to "real" ids when a save is performed. 2. An in-memory data store with the EntityServerFakeBackingStore. 3. Query functionality with the EdmQueryExecutorFake. 4. Save functionality with the EdmSaveExecutorFake. These are not stubs. They are rich implementation that provide functionality almost identical to that of their "real" counterparts. Faked queries and saves return virtually the same results you would expect from a real data store, but instead use a fake data store, called the EntityServerFakeBackingStore. Once you populate this store, you can issue standard queries and saves from an EntityManager created for the context without any additional code. Note that other DevForce extensions, such as IEntityLoginManager, are not faked, so either DevForce defaults or your custom implementations will be used for those. You can create multiple named fake contexts, and custom contexts based on the standard CompositionContext.Fake. Each unique context uses its own backing store. With a custom fake context you can supply additional fake functionality of your own. Simple Faking At the simplest, all you need to do is create an EntityManager for the default fake context and issue queries and saves against it. Of course, unless you've pre-populated the fake backing store queries will return no data until you've first saved some entities. Here we create a new EntityManager and populate the backing store with a few test entities: C# var em = new DomainModelEntityManager(compositionContextName: CompositionContext.Fake.Name); PopulateBackingStore(em); VB Dim em As New DomainModelEntityManager(compositionContextName:= CompositionContext.Fake.Name) PopulateBackingStore(em) C# private void PopulateFakeBackingStore(EntityManager em) { var customer = new Customer { CompanyName = "Test Company" }; em.AddEntity(customer);
var employee = new Employee { FirstName= "Fred", LastName = "Smith"}; em.AddEntity(employee);
for (int i = 0; i < 5; i++) { var salesOrder = new OrderSummary { Employee=employee, Customer = customer}; em.AddEntity(salesOrder); } em.SaveChanges(); } VB Private Sub PopulateFakeBackingStore(ByVal em As EntityManager) Dim customer = New Customer() With {.CompanyName = "Test Company"} em.AddEntity(customer)
Dim employee = New Employee() With {.FirstName = "Fred", .LastName = "Smith"} em.AddEntity(employee)
For i As Integer = 0 To 4 Dim salesOrder = New OrderSummary() With {.Employee = employee, .Customer = customer} em.AddEntity(salesOrder) Next em.SaveChanges() End Sub Once the backing store is populated with test data we can then use the EntityManager as usual. Here we clear it first, to ensure that the EntityManager is querying from the backing store. C# em.Clear(); // clear cache; no memory of prior entities
var query = em.Customers.Include(c => c.OrderSummaries) .Where(c => c.OrderSummaries.Any(os => os.Employee.FirstName == "Fred")); VB em.Clear() ' clear cache; no memory of prior entities
Dim query As em.Customers.Include(Function(c) c.OrderSummaries).Where _ (Function(c) c.OrderSummaries.Any(Function(os) os.Employee.FirstName = "Fred")) Working directly with a fake backing store You can access a fake backing store via a fake CompositionContext, for example: C# var store EntityManager.CompositionContext.GetFakeBackingStore(); VB Dim store = EntityManager.CompositionContext.GetFakeBackingStore() Local vs. remote stores Different backing stores are used in 2-tier vs. n-tier applications, EntityServerFakeBackingStore.Local and EntityServerFakeBackingStore.Remote. The Local store, which is not available in Silverlight applications, allows you to perform synchronous queries and saves when not using a remote EntityServer. The Remote store is used with Silverlight applications and any others using a remote EntityServer. Generally, you won't be concerned whether the store in use is local or remote unless you're working directly with it, for example to clear or populate it. Store methods The entities stored and retrieved by the EntityServerFakeBackingStore are actually held in an EntityCacheState. The EntityServerFakeBackingStore provides methods to allow you to work directly with both it and the underlying EntityCacheState. You can use Save or SaveAsync to save the store to a file or stream, and Restore or RestoreAsync to restore data into the store. Clear or ClearAsync can be used to clear the store (for example to clear the store between tests). Note that only the asynchronous versions of these methods are available in Silverlight applications. The EntityCacheState makes it easy to load test data into the backing store, possibly from a test database. The EntityCacheState can be initially loaded from an EntityManager, and allows you to save to and restore from a file or stream. Using these techniques you can write a small helper method or application, a "TestDataMother" if you will, which is responsible for creating and loading test data. Troubleshooting If you deploy an application using faking to IIS, you must also modify a web server setting to allow services using the fake composition context name to start successfully. Set allowDoubleEscaping to true: XML <system.webServer> <security> <requestFiltering allowDoubleEscaping="true"/> </security> </system.webServer> Additional resources The BookShelf sample application contains an example of a "TestDataMother". The known type primer Last modified on April 28, 2011 16:00 Contents What is a known type? When should I care? What does DevForce provide automatically? How to indicate a known type? Troubleshooting You've landed here because you're trying to use some simple feature of DevForce - say a remote server method or POCO type - and you've gotten a strange error message about "known types". Here we'll try to dispel some of the misconceptions and explain a few of the mysteries of why known types are so important in your DevForce application.
What is a known type? In an n-tier application, such as a DevForce Silverlight application or any application using a remote EntityServer, data must be moved between tiers: it must be serialized for transmission and deserialized by the recipient. In DevForce, this serialization and deserialization is done using the .NET DataContractSerializer (DCS). The serializer must be able to transmit queries, entities, POCOs, RSM arguments and return values - anything you send to or receive from the server. The DCS uses contracts to tell it how to process the data it receives; whenever the contract is imprecise, for example when a property is typed as Object, or as an interface or abstract type, the DCS needs to be told what types are known to be in the object graph, hence known types. In DevForce it's not only properties which might be vague, but method arguments and return values too may not be strongly typed by the underlying DevForce contracts, and must instead be made known at run time. It should be obvious that a known type must also be serializable. A type is generally serializable if it has a parameterless public constructor, or it's marked up with DataContract and DataMember attributes. Here's more information from Microsoft on what can be serialized with the DCS. When should I care? Login - You may have noticed two things about the Login method: it takes an ILoginCredential, and from that credential produces an IPrincipal. Two interfaces, whose concrete implementations must be known at run time. Therefore, any custom ILoginCredential or IPrincipal (or IIdentity) must be made known to DevForce as a known type. Fortunately, DevForce will specifically look for custom implementations of these interfaces and add them to a list of known types it builds for the serializer so you do not have to do anything special, but it's still worth remembering that these are known types. Remote Server Methods - If you've created any remote server methods (sometimes called either RSM or RPC), you've also likely passed data both as arguments to the methods and as return values. Remember the signature for the RSM: C# public delegate Object ServerMethodDelegate(IPrincipal principal, EntityManager serverEntityManager, params Object[] args); VB Public Delegate Function ServerMethodDelegate(ByVal principal As IPrincipal, _ ByVal serverEntityManager As EntityManager, ByVal ParamArray args() As Object) As Object A method which takes zero or more user-specified objects, and returns an object (which might be a list or graph of objects). Since the signature is not strongly typed, everything passed into or out of the method must be designated as a known type. In this case, DevForce won't do this discovery for you - you must tell DevForce, or the serializer, the known types involved. Push (Notification Service) - As with remote server methods, the types which can be sent and received are not known until run time. When registering for the service you can pass zero or more objects; the notification data returned by the service is also an object array. Since the signatures are not strongly typed, the concrete types used must all be designated as known types. POCO - POCO types which will be passed between tiers must also be identified as known types. If your POCO type has a Key attribute DevForce will automatically add your POCO type as a known type, but it's good to remember that POCO types must be known types. Projection into type - When you project query results into a custom type, that custom type must be designated as a known type. Query Contains clause - In LINQ a Contains clause is used in place of the SQL IN operator. The list on which the Contains is defined must be a known type if the query will be sent to the server. DevForce automatically defines a known type for all List<T> where T is a numeric type, a string, a DateTime or a GUID. If your list contains some other type the list for this type must be designated as a known type. Note that primitive types are already known to the DCS and if you are using these, for example as arguments to or a return value from an RSM, you do not need to designate the type as a known type. Here's a list from Microsoft on the primitives it automatically recognizes. What does DevForce provide automatically? At start up, and upon recomposition when an on-demand XAP is downloaded, DevForce will automatically build a list of known types to be provided to the serializer. This list is actually provided to any DCS which might be used: the serializer used to transmit data between tiers, a serializer used when creating or restoring an EntityCacheState, and serializers used for specialized cloning. This list contains: All entity and complex types found in any entity models, and List<T> and RelatedEntityList<T> for each. List<T> where T is a numeric, string, DateTime or GUID. All DevForce types expected to be transmitted between tiers. DateTimeOffset and StringComparison. Any discovered ILoginCredential, IDataSourceKeyResolver, IIdGenerator, IPrincipal, and IIdentity implementations. Types discovered via IKnownType, DiscoverableTypeMode.KnownType and IKnownTypeProvider. You may notice that any types marked with the .NET KnownType attribute are not included in this list. The serializer has two ways of obtaining known types: one is the list provided to it upon construction, and second is via the KnownType attributes it finds within the object graph being serialized/deserialized. The list DevForce builds is the list passed to the serializer constructor; but your object graph can include KnownType attributes to directly tell the serializer what to expect within the object graph. Note that the KnownType attribute is not used to indicate that the type itself is a known type, but instead to indicate other types are known types. This is a subtle but important distinction: you can tell DevForce about known types (via DevForce interfaces) and DevForce will in turn tell the serializer; or you can directly tell the serializer (via the .NET KnownType attribute). The end result is the same: the serializer will use the known type information whenever the contract definition is imprecise. How to indicate a known type? Have the type implement the I deaBlade.EntityModel.I KnownTypemarker interface When you have control over the type (i.e., it's not a .NET type or one defined in a third-party assembly), using the marker interface is often the simplest implementation. C# public class OrderRequestInfo : IKnownType { public int Top { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } VB Public Class OrderRequestInfo _ Implements IKnownType Public Property Top() As Integer Public Property StartDate() As Date Public Property EndDate() As Date End Class Decorate the type with the I deaBlade.EntityModel.DiscoverableTypeattribute This attribute is functionally equivalent to the IKnownType marker interface. C# [DiscoverableType(DiscoverableTypeMode.KnownType)] public class OrderRequestInfo { public int Top { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } VB <DiscoverableType(DiscoverableTypeMode.KnownType)> _ Public Class OrderRequestInfo Public Property Top() As Integer Public Property StartDate() As Date Public Property EndDate() As Date End Class Return the type from an I deaBlade.EntityModel.I KnownTypeProvider The IKnownTypeProvider allows you to specify any type - your own, .NET or third party types - so is useful either when you don't want to modify type declarations or cannot. Any number of IKnownTypeProviders may be defined. C# public class KnownTypeProvider : IKnownTypeProvider {
public IEnumerable<Type> AddKnownTypes() {
var list = new Type[] { typeof(OrderRequestInfo), typeof(OrderResponseInfo), typeof(List<DateTimeOffset>) };
return list; } } VB Public Class KnownTypeProvider Implements IKnownTypeProvider
Public Function AddKnownTypes() As IEnumerable(Of Type)
Dim list = New Type() {GetType(OrderRequestInfo), _ GetType(OrderResponseInfo), GetType(List(Of DateTimeOffset))}
Return list End Function End Class Add the System.Runtime.SerializationKnownTypeAttribute to another type (such as an entity) Here you're marking up your object graph to indicate to the serializer what other types to expect. DevForce is unaware of types marked with the KnownType attribute, so this cannot be used when defining POCO types. C# [KnownType(typeof(OrderRequestInfo))] public partial class OrderSummary { ... } VB <KnownType(GetType(OrderRequestInfo))> _ Partial Public Class OrderSummary ... End Class Troubleshooting So you've received the dreaded formatter exception, something like "The formatter threw an exception while trying to deserialize the message ..." and it goes on for 3 or 4 lines. Somewhere in there it mentions a type (or contract name) and says to add the type to the list of known types. What? How? Didn't I already do that? This message is coming straight from the serializer, and does tell you exactly what it found to be wrong, albeit in somewhat confusing language. 1. Make sure that the type is defined on both client and server. This is easy to overlook, but should be intuitively obvious. Both the client and server should be aware of any types to be passed between them. With Silverlight applications, DevForce ensures this of entities in the model by including a link to the generated code in the Silverlight application. You can do the same with your own types: define the type in a file in a Desktop/Server project and create a link to the file in a Silverlight project. DevForce will perform type mapping between tiers, so as long as the namespace and type name are the same, you may place the type definition in any assembly. 2. Make sure the type is serializable. If your type has a non-default constructor (in other words a constructor which accepts arguments) then it is not serializable. You can easily remedy this by either adding a default constructor or marking up the class with DataContract and DataMember attributes so that you can specifically control which members are serialized. If a type which cannot be serialized is included as a known type the EntityServer service will not start. You'll see a message in the server's debug log which states the service "cannot be activated due to an exception during compilation." The message will also include the type at fault. 3. Make sure the type is somehow marked as a known type, given the information above on how to indicate a known type. Note that a type, T, and List<T> are not the same thing. If you return a List<T> from an RSM, then the known type is List<T>, not T. Also note that to the serializer, List<T> and T[] are different things, but they may unfortunately have the same contract. Since DevForce automatically adds List<T> for most primitive types to the known type list, you will receive an error if you add T[] for a primitive type as a known type. This also means that in queries with a contains clause you should use a List<T> and not T[]. Additional techniques
POCO Last modified on March 16, 2011 13:32 Introduction In addition to objects based on classes inheriting from IdeaBlade.EntityModel.Entity and generated by the DevForce EDMX Extension, you can also use Plain Old CLR Objects (POCOs) with DevForce. For our purposes, a POCO class is any class that does not contain any DevForce specific code. Any POCO classes must be deployed on both the client and the server and must be contained in one of the assemblies routinely searched by DevForce. An additional POCO service provider, must be supplied server-side, marked with the EnableClientAccess attribute. This class will contain methods to perform the server-side CRUD operations for your custom objects. 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. Where possible we have adopted Microsoft's "RIA Services" naming conventions and attributes. POCO's and the EntityManager Last modified on March 22, 2011 16:18 POCO entities are managed by DevForce in the same fashion as Entity Framework sourced entities. The diagram below illustrates this. The DevForce EntityManager effectively provides two categories of objects that it can host and provides 'almost' identical services to both.
Implement a POCO class Last modified on March 22, 2011 19:08 Contents POCO class rules Example Other issues A POCO class is a .NET class that has no visible dependencies on DevForce. In order for DevForce to be able to query and save instances of such types there are a few simple additional rules that need to be followed.
POCO class rules
POCO types must be "known" types. ( KnownType ) Indicating that a type is a "known" type allows it to be discovered by the DevForce infrastructure. There are several ways to identify the type as a "known" type to DevForce: (1) Decorate a property (or multiple properties) of the class with the Key attribute; or (2) Have the type implement the IKnownType interface; or (3) Implement the IKnownTypeProvider interface. POCO types without a Key attribute will NOT be added to the EntityManager cache and cannot make use of EntityAspect semantics. This is because the EntityManager stores and indexes entities by thier keys and therefore objects without key information cannot be managed. Note that keyless POCO's are actually useful when you just want to bring down temporary data for immediate processing and then throw it away. Because keyless POCO's are not cached they will get garbage collected as soon as they go out of scope.
POCO types must be serializable. 1) Mark the class and members with the DataContract and DataMember attributes, or 2) The public properties of the class will be serializable if the class has a public parameterless constructor. This technique is used below. Implement INotifyPropertyChanged if you want EntityState management of your POCO object. Note that you can always manage your POCO's EntityState manually ( described later) if you would rather not implement INotifyPropertyChanged. Example
POCO types are most commonly used to create entities that wrap data that is not stored in a relational database. Here is a class representing a State of the United States. At runtime, you might, for example, create a collection of these state instance from an XML data file deployed on the server: C# using System; using System.ComponentModel.DataAnnotations;
namespace DomainModel {
public class USState { public USState() { }
[Key] public string Abbrev { get { return _abbrev; } set { _abbrev = value; } }
public string Name { get { return _name; } set { _name = value; } }
<Key()> Public Property Abbrev() As String Get Return _abbrev End Get Set(ByVal value As String) _abbrev = value End Set End Property
Public Property Name() As String Get Return _name End Get Set(ByVal value As String) _name = value End Set End Property
Private _abbrev As String Private _name As String End Class End Namespace Note that we could have omitted the use of the Key attribute if we had made the class implement the IKnownType interface. C# public class USState: IdeaBlade.EntityModel.IKnownType { ... } VB Public Class USState Implements IdeaBlade.EntityModel.IKnownType ... End Class This approach can be useful if you will have many classes that inherit from a base class, and you wish only to mark the base class. A third option is to implement the DevForce IKnownTypeProvider interface and return your list of known types: C# public class KnownTypeProvider : IKnownTypeProvider { public IEnumerable<Type> AddKnownTypes() { return new Type[] { typeof(USState) } } } VB Public Class KnownTypeProvider Implements IKnownTypeProvider Public Function AddKnownTypes() As IEnumerable(Of Type) Return New Type() { GetType(USState) } End Function End Class Other issues One issue that can arise is the need to mark up a type where the return type of one of the data members is actually the base type of the object expected to be actually returned. In this case, the KnownType attribute may be used to mark up a type with information about any of its serializable members where the declared type of the member is different from the actual type of the member. Suppose, for example, that your State class includes a Cities property which is typed as an object but actually returns a List<Cities>. This will confuse the serializer and make it serialize the property improperly. To enable the property to be serialized properly, you must attribute it as follows: C# [DataContract] [KnownType(typeof(List<City>)] public class State { public State() {} //[... snip] [DataMember] public object Cities { get { return _cities; } set { _cities = value; } }
private List<City> _cities; } VB <DataContract, KnownType(GetType(List(Of City))> Public Class State Public Sub New() End Sub '[... snip] <DataMember> Public Property Cities() As Object Get Return _cities End Get Set(ByVal value As Object) _cities = value End Set End Property
Private _cities As List(Of City) End Class Note that the KnownType attribute used above is not a DevForce specific attribute but is part of .NET's WCF serialization stack. It should not be confused with the DevForce IKnownType interface also described above. The two, while both related to the concept of known types, are intended for very different purposes. Implement query and save services Last modified on March 16, 2011 18:41 Contents Example In order to provide server side querying and saving capabilities for POCO objects, a POCO Service Provider class or classes must be provided. This POCO Service Provider class can be named anything and you may have many such classes. The one rule is that each of these classes be marked with an EnableClientAccess attribute.
Example
The example below shows a simple POCO Service Provider, that provides the ability to perform server side queries and saves against a collection of POCO instances of 'USState's. C# namespace DomainModel {
[EnableClientAccess] public class USStatesServiceProvider {
public IEnumerable<USState> GetStates() { ... }
public void InsertState(State entity) { ... }
public void UpdateState(State current, State original) { ... }
<EnableClientAccess> Public Class USStatesServiceProvider
Public Function GetStates() As IEnumerable(Of USState) ... End Function
Public Sub InsertState(ByVal entity As State) ... End Sub
Public Sub UpdateState(ByVal current As State, ByVal original As State) ... End Sub
Public Sub DeleteState(ByVal entity As State) ... End Sub End Class End Namespace All of the actual method code is stubbed out in the example so that the overall structure can be seen. Any of these methods can be omitted as long as the corresponding functionality is not needed. So if we never planned on inserting, deleting or updating a 'USState' then the InsertState, UpdateState and DeleteState methods could all be ommited. Details of how such methods can be implemented are described later. In the example below, the method names GetStates, InsertState, UpdateState, DeleteState are all 'convention' based. More explicit linkage between service methods and the underlying types can also be specified.
Provide query support Last modified on April 22, 2011 17:33 Contents Example Finding providers by convention The Query attribute alternative Query methods with parameters When to use parameterized query methods rather than LINQ expressions IQueryable Query methods Include syntax is not supported for POCO queries In order to provide server side support for POCO queries, a 'query' method or methods must be supplied within a class marked with the EnableClientAccess attribute. Connecting these server side methods to corresponding client side queries is handled either by 'convention' based method naming or by explicit attribute markup.
Example
The example below shows a simple POCO Service Provider, that provides the ability to perform server side queries against a collection of State POCO entities. C# namespace DomainModel {
[EnableClientAccess] public class PocoServiceProvider {
public IEnumerable<State> GetStates() { if ( _states == null) { _states = ReadStatesData("states.xml"); } return states; }
<EnableClientAccess> Public Class PocoServiceProvider
Public Function GetStates() As IEnumerable(Of State) If _states Is Nothing Then _states = ReadStatesData("states.xml") End If Return states End Function
Private Shared Function ReadStatesData(ByVal embeddedFileName As String) As IEnumerable(Of State) ... End Function
Private Shared _states As List(Of State) = Nothing End Class End Namespace Finding providers by convention The GetStates() method retrieves the data requested by a query (typically a query from the client). The method name is important. It follows the same query naming conventions that DevForce uses to locate named query methods. In brief, DevForce looks for a method whose name begins with a known prefix and is followed by an EntitySet name. The EntitySet name can be anything that makes sense to you. Most developers use the plural of the POCO entity type name. Here are the recognized prefixes: Get Query Fetch Retrieve Find Select The GetStates method conforms to the convention: "Get" is the prefix; "States" is the EntitySet name. The following query will be routed to the GetStates server-side query method: C# var statesQuery = new EntityQuery<State>("States", anEntityManager); VB Dim statesQuery = New EntityQuery(Of State)("States", anEntityManager) The Query attribute alternative The server method doesn't have to follow the naming convention. It can be named as you please as long as it's adorned with the Query attribute. C# [Query] public IEnumerable<State> ReturnAllStates() { IEnumerable<State> states = ReadStatesData("states.xml"); return states; } VB <Query()> _ Public Function ReturnAllStates() As IEnumerable(Of State) Dim states As IEnumerable(Of State) = ReadStatesData("states.xml") Return states End Function You must adjust the client-side query to specify the full method name as the EntitySet name: C# var statesQuery = new EntityQuery<State>("ReturnAllStates", anEntityManager); VB Dim statesQuery = New EntityQuery(Of State)("ReturnAllStates", anEntityManager) Query methods with parameters Methods with parameters are supported as well. For example, an additional overload to the GetStates() method above that only returns states with a population size greater than a size passed in might be written as shown below. (Naturally, this would require that the internal ReadStatesData() method be modified as well.) C# public IEnumerable<State> GetStates(long minPopulationSize) { IEnumerable<State> states = ReadStatesData("states.xml", minPopulationSize); return states; } VB Public Function GetStates(ByVal minPopulationSize As Long) _ As IEnumerable(Of State) Dim states As IEnumerable(Of State) = ReadStatesData("states.xml", _ minPopulationSize) Return states End Function On the client side, parameters may be specified by using one of the EntityQuery.AddParameter() overloads. The following snippet calls the parameterized GetStates() method to return just those states with a population greater than one million people: C# var query = new EntityQuery<State>("States", anEntityManager); query.AddParameter(1000000); VB Dim query = New EntityQuery(Of State)("States", anEntityManager) query.AddParameter(1000000) Any number of query parameters are permitted, and the standard .NET overload resolution rules apply. This means that the order and type of the parameters are checked to find appropriate matches, with type coercion occurring as required. When to use parameterized query methods rather than LINQ expressions The following two queries will return the same results: C# var query1 = new EntityQuery<State>("States", anEntityManager); query.Where(state => state.Population > 1000000);
var query2 = new EntityQuery<State>("States", anEntityManager); query.AddParameter(1000000); VB Dim query1 = New EntityQuery(Of State)("States", anEntityManager) query.Where(Function(state) state.Population > 1000000)
Dim query2 = New EntityQuery(Of State)("States", anEntityManager) query.AddParameter(1000000) Furthermore, in both cases, the restriction to those states with greater than one million population occurs on the server, not the client. So the question arises: is one to be preferred over the other? The answer usually depends upon how the server-side method itself is implemented. In general, unless the server-side method can internally use the query parameter to restrict its own query against some back-end data store, query parameters have no advantage over LINQ query restrictions. In fact, LINQ queries are far more flexible and intuitive to work with under most circumstances. Nevertheless, there will be cases where a back-end data stores ability to optimize some queries will yield sufficient performance improvement to justify the use of query parameters. For example, consider the Windows file systems 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. IQueryable Query methods The GetStates POCO query returns an IEnumerable of State. That's fine for this example because there are only 50 state objects and they are sourced from an XML file. The impact of a State query on the server is small whether the client asks for one state or all states. But what if the POCO is of type Order and we source orders from a database. If GetOrders returns an IEnumerable<Order>, every time the client queries for orders the server retrieves every order in the database. There could be millions of orders. Every client query would force the server to retrieve those millions of orders. Even a filtered client query such as C# var ordersQuery = new EntityQuery<Order>("Orders", anEntityManager).Where(o => o.OrderID == 42); VB Dim ordersQuery = New EntityQuery(Of Order)("Orders", anEntityManager).Where(function (o) o.OrderID = 42) causes the server to first retrieve every order from the database and then filter down to the one order with ID of 42. That's not going to perform well! Suppose the Order data source can be queried with an IQueryable form of LINQ. Raw Entity Framework can do it with LINQ-to-Entities; there is LINQ for NHibernate. You then write an IQueryable<Order> GetOrders() query method on the server that returns the LINQ query. DevForce appends the client's "Where" clause as before ... but now, when the query is executed on the server, the query is composed such that the data source only returns the one order. The difference between IEnumerable and IQueryable bears repeating. Both example queries return a single order to the client. The supporting IQueryable server-side query method retrieves only one order from the database; the IEnumerable server-side query method retrieves every order from the database before filtering down to the one desired order. That's a huge performance - and scaling - improvement. It pays to write POCO query methods that return IQueryable<T> when possible. Include syntax is not supported for POCO queries
The Include() syntax on a POCO entity query is not currently implemented. The call will compile but will not do anything. Provide save support Last modified on March 18, 2011 14:31 Contents PocoSaveMode Adapter-Based Saves Convention-Based Saves DevForce provides the ability to save POCO instances by dispatching save calls to a set of server side interceptor methods. These methods come in two flavors and will be referred to as either adapter-based or convention-based implementations. By default, DevForce will attempt to locate an adapter-based implementation; if unsuccessful, it will then look for a convention-based implementation.
PocoSaveMode The implementation mechanism can also be specified explicitly by setting the PocoSaveMode property of the SaveOptions instance passed into the SaveChanges call: C# var so = new SaveOptions(); so.PocoSaveMode = PocoSaveMode.UseEntitySaveAdapter; myEntityManager.SaveChanges(so); VB Dim so = New SaveOptions() so.PocoSaveMode = PocoSaveMode.UseEntitySaveAdapter myEntityManager.SaveChanges(so) The PocoSaveMode is an enumeration with the following values: Value Description Default Use an EntitySaveAdapter subclass if found, otherwise use save methods discovered via convention. UseEntitySaveAdapter Use an implementation of EntityServerPocoSaveAdapter. UseMethodsDiscoveredViaConvention Use methods discovered via convention. Adapter-Based Saves The adapter-based mechanism requires the existence of a server-side class that inherits from the IdeaBlade.EntityModel.Server.EntityServerPocoSaveAdapter. If such a class is found, a single instance of this class will be created for each SaveChanges call, and the appropriate methods corresponding to each insert, update, or delete will be called for each entity to be saved. A single null-parameter constructor is required. The following bare-bones version of an EntitySaveAdapter implementation shows the methods you have available to override: C# public class PocoSaveAdapter : EntityServerPocoSaveAdapter { public PocoSaveAdapter() { }
public override void BeforeSave(IEnumerable entities, SaveOptions saveOptions) { }
public override void AfterSave() { }
public override void InsertEntity(object entity) { }
public override void UpdateEntity(object entity, object originalEntity) { }
public override void DeleteEntity(object entity) { } } VB Public Class PocoSaveAdapter Inherits EntityServerPocoSaveAdapter Public Sub New() End Sub
Public Overrides Sub BeforeSave(ByVal entities As IEnumerable, ByVal saveOptions As SaveOptions) End Sub
Public Overrides Sub AfterSave() End Sub
Public Overrides Sub InsertEntity(ByVal entity As Object) End Sub
Public Overrides Sub UpdateEntity(ByVal entity As Object, ByVal originalEntity As Object) End Sub
Public Overrides Sub DeleteEntity(ByVal entity As Object) End Sub End Class 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 BeforeSave and AfterSave methods are called exactly once for each SaveChanges call and may be used for 'setup' and 'teardown' logic. Convention-Based Saves The convention-based mechanism requires the existence of a server-side class with the EnableClientAccess attribute. This class will include insert, update, and delete methods named according to the conventions defined below. It must also include a single null- parameter constructor. If such a class is found, a single instance of it will be created for each SaveChanges call, and the appropriate methods corresponding to each insert, update, or delete will be called for each entity to be saved. For each type for which a persistence operation is provided, up to three methods must be written. The name of the method must begin with one of the prefixes defined below. The method must also conform to the signature defined below. If you do not expect to save objects that require one or more of these signatures, then they do not have to be implemented. In other words, if you never plan to delete Foo type objects, then you do not need to implement the Delete method. Insert Method Prefixes: Insert, Add, or Create Signature: void InsertX(T entity), where T is an entity Type Update Method Prefixes: Update, Change, or Modify Signature: void UpdateX(T current, T original), where T is an entity Type Delete Method Prefixes: Delete or Remove Signature: void DeleteX(T entity), where T is an entity Type Below is an example implemention showing the use of the required conventional method names and signatures. InsertFoo could equally be named AddFoo or CreateFoo; UpdateFoo could be named ChangeFoo or ModifyFoo; and so forth. C# [EnableClientAccess] public class PocoSaveAdapterUsingConventions { public PocoSaveAdapterUsingConventions() { } public void InsertFoo(Foo entity) { // insert logic for any Foo entities } public void InsertBar(Bar entity) { // insert logic for any Bar entities } public void UpdateFoo(Foo current, Foo original) { // update logic for any Foo entities } public void UpdateBar(Bar current, Bar original) { // update logic for any Bar entities } public void DeleteFoo(Foo entity) { // Delete logic for any Foo entities } public void DeleteBar(Bar entity) { // Delete logic for any Bar entities } } VB <EnableClientAccess()> _ Public Class PocoSaveAdapterUsingConventions Public Sub New() End Sub Public Sub InsertFoo(ByVal entity As Foo) ' insert logic for any Foo entities End Sub Public Sub InsertBar(ByVal entity As Bar) ' insert logic for any Bar entities End Sub Public Sub UpdateFoo(ByVal current As Foo, ByVal original As Foo) ' update logic for any "Foo entities End Sub Public Sub UpdateBar(ByVal current As Bar, ByVal original As Bar) ' update logic for any "Bar entities End Sub Public Sub DeleteFoo(ByVal entity As Foo) ' Delete logic for any "Foo entities End Sub Public Sub DeleteBar(ByVal entity As Bar) ' Delete logic for any "Bar entities End Sub End Class
Make your POCO query a first class citizen Last modified on March 24, 2011 00:09 Contents Create the query directly Create an extension method Extend your strongly typed EntityManager partial class Querying for POCO entities from the client-side involves exactly the same process as is used for non-POCO entities, with the single exception that DevForce will not generate a 'root' property on your EntityManager to start your queries from. There are three ways around this:
Create the query directly Your first choice is to simply bypass the idea of a 'root' query property on your EntityManager and create the query directly. C# var baseQuery = new EntityQuery<USState>("USStates", anEntityManager); var queryForstatesStartingWithA = query.Where(s => s.Abbrev.StartsWith("A"); VB Dim baseQuery = New EntityQuery(Of USState)("USStates", anEntityManager) Dim queryForstatesStartingWithA = query.Where(Function(s) s.Abbrev.StartsWith("A") Create an extension method The next choice is to create an extension method that adds a "USStates" method to every EntityManager. For example: C# namespace DomainModel { public static class EmExtensions { public static EntityQuery<USState> USStates(this EntityManager em) { return new EntityQuery<USState>("USStates", em); } } } VB Namespace DomainModel Public Module EmExtensions <System.Runtime.CompilerServices.Extension> _ Public Function States(ByVal em As EntityManager) As EntityQuery(Of USState) Return New EntityQuery(Of USState)("USStates", em) End Function End Module End Namespace With this extension method, you can now query for USStates with the following syntax: C# var queryForstatesStartingWithA = anEntityManager.USStates().Where(s => s.Abbrev.StartsWith("A")); VB Dim queryForstatesStartingWithA = _ anEntityManager.USStates().Where(Function(s) s.Abbrev.StartsWith("A")) This is very similar to what you do with DevForce entities, except that, since USStates is an extension method, you will have to include the parentheses -- anEntityManager.States(), which you do not have to do with autogenerated query properties. ie. anEntityManager.USStates.Where() Extend your strongly typed EntityManager partial class
An obvious alternative to this process is to add a "USStates" property to your own "custom" partial class that extends your strongly typed EntityManager. This makes "USStates" exactly like any other "root" entity query available from the EntityManager, but does require duplicate code if you expect to use "States" from a different strongly typed EntityManager. C# public partial class DomainModelEntityManager : IdeaBlade.EntityModel.EntityManager { public EntityQuery<USState> USStates { get { return new EntityQuery<USState>("USState", this); } } } VB Partial Public Class DomainModelEntityManager Inherits IdeaBlade.EntityModel.EntityManager Public ReadOnly Property USStates() As EntityQuery(Of USState) Get Return New EntityQuery(Of USState)("USState", Me) End Get End Property End Class 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 Obtain access to entity services Last modified on March 21, 2011 17:37 Contents Access to EntityAspect Wrap your POCO Implementing IHasEntityAspect Why does implementing IHasEntityAspect improve performance? Objects from DevForce-generated Entity classes have an EntityAspect property through which a number of important operations and useful pieces of metadata can be obtained. POCO objects, by their nature do not have such a property. There are, however, other ways to get access to such services.
Access to EntityAspect In order to get an EntityAspect for any POCO entity, there are two choices: Wrap your POCO entity at runtime with a class that provides access to EntityAspect services. Implement the IHasEntityAspect interface on your POCO entity. o The downside of this is that your POCO entity is not longer a 'pure' POCO entity because you will have added DevForce specific code to it. o The upside is that your POCO entities have an EntityAspect property just like any other DevForce entity and performance is improved. Wrap your POCO
The EntityWrapper.Wrap static method may be used to get access to a runtime wrapper that provides an EntityAspect property. C# var stateWithAspect = EntityWrapper.Wrap(aState); VB Dim stateWithAspect = EntityWrapper.Wrap(aState) Having done that, you can then ask questions like the following C# bool isModified = stateWithAspect.EntityAspect.IsModified(); VB Dim isModified As Boolean = stateWithAspect.EntityAspect.IsModified() and of course, use any of the other facilities of the EntityAspect. Implementing IHasEntityAspect The IHasEntityAspect is a very simple interface that contains a single property of type EntityAspect. To add this implementation to any given POCO type simply mark the class with the interface and add a EntityAspect property with a default 'getter' and 'setter' as shown below: C# public class USState : IHasEntityAspect { //*snip+ [IgnoreDataMember] public EntityAspect EntityAspect { get; set; } } VB Public Class USState _ Implements IHasEntityAspect '*snip+ <IgnoreDataMember> _ Public Property EntityAspect() As EntityAspect End Class Once you have implemented this interface, your POCO entity will have direct access to this 'new' EntityAspect property, and DevForce will insure that it is properly initialized and configured. Note that we have included the IgnoreDataMember attribute to the EntityAspect property we just created. Basically we are telling WCF that we do NOT want the EntityAspect property to be serialized. Use of this attribute is only necessary if we are relying on default WCF serialization, i.e. we are not marking up our POCO entity with DataContract and DataMember attributes. If we are using DataContract and DataMember attributes then we simply need to make sure not to mark this property with a DataMember attribute. Why does implementing IHasEntityAspect improve performance?
DevForce has to keep track of its own bookkeeping data about each POCO instance. By default, DevForce uses a hashtable to maintain this data, and therefore needs to perform a lookup operation anytime you need access to any EntityAspect services. What the IHasEntityAspect interface does is provide a place for DevForce to keep its own bookkeeping data about each POCO instance on the instance itself, hence no need for the hashtable or the lookup. POCO summary - Things to remember Last modified on March 16, 2011 13:34 POCO classes must be included in both the server- and client-side assemblies. Put them in the server-side project and link them into the client-side project. Include a POCO ServiceProvider class server-side. Include EntityManager extensions client-side. You can use all forms of LINQ query against these objects. If your POCO class has a primary key (designated with the [Key] attribute, instances of it will be stored in the EntityManager cache, and can be updated, deleted, or inserted there. If your POCO class has no key, instances can be retrieved but will not be placed in the EntityManager cache. Implement IHasEntityAspect to get EntityAspect property and associated functionality on your almost POCO entity.
OData Last modified on June 08, 2011 14:17 DevForce 6.0.7 or later provides the ability to expose a domain model through the Open Data Protocol (OData). OData is an open web protocol for querying and updating data. The protocol allows for a consumer to query a datasource over the HTTP protocol and get the result back in formats like Atom, JSON or plain XML, including pagination, ordering or filtering of the data. For more information on OData and client libraries for various platforms, visit www.odata.org. Prerequisites DevForce 6.0.7 Code sample Learn how to enable OData with this code sample. The complete walkthrough is available here. Enabling OData Last modified on March 25, 2011 13:04 Contents Getting started Connecting to the data Enabling OData Enabling OData for a DevForce model allows for non .NET clients to access the same back- end as your DevForce .NET clients. The programming model will be different from DevForce and depends on the OData client implementation on the chosen platform, but it avoids the need for developing yet another back-end or at a minimum some sort of a translation layer in front of the DevForce back-end. In this topic, we will demonstrate how to expose a DevForce domain model through OData and access it from a client. Note: DevForce 6.0.7 or later is required for this tutorial. Getting started Lets begin by creating a new project. Well be using Visual Studio 2010 and creating a Web Application. Create a new project by selecting DevForce BOS Web Application from the New Project window.
The template creates one project for us. The DevForce BOS Web Application is the middle tier of a typical n-tier DevForce application. At the end of this tutorial it will be able to serve regular DevForce clients as well as OData clients. Connecting to the data In this tutorial, we are going to use our usual suspect, the NorthwindIB database. At this stage we are assuming basic familiarity with how to create a data model using ADO.NET Entity Framework. If you are not familiar with creating a data model, please complete one of our intro tutorials first. We want to name the model NorthwindIB, generate it from the database and select only the Customer and Order tables to keep it small and manageable. If everything went well, the result should look like this:
Enabling OData For all intents and purposes, at this point we have a functioning DevForce back-end and could start building a DevForce client application. Instead, we are going to expose this back-end via OData with a two-step process.
For the first step, we need to enable OData in our domain model. Open NorthwindIB.edmx and click anywhere in the whitespace to select the model, then hit F4 to bring up the model properties. Under the DevForce Code Generation category you will find a new option called OData Enabled. By default the option is set to False. Go ahead and change it to True, then save.
Lets look at what this option did for us. First, it added a couple of attributes to our Entity classes to make them usable in a WCF Data Service. Second, it generated a partial class for our strongly typed EntityManager. Open NorthwindIBEntities_IUpdatable.cs. In this file, youll find an implementation of System.Data.Services.IUpdatable. The WCF Data Service calls upon this implementation to handle the CUD operations. This file gets generated only once, so you can make customization without having to worry about them getting wiped out. Now, whats this WCF Data Service? The WCF Data Service is part of the .NET framework and handles all the OData mechanics. In order for the WCF Data Service to do its magic, it requires a compatible data container, which provides the actual data and supports optional updates. Enabling OData in our data model turned the EntityManager into just such a container. The implementation of System.Data.Services.IUpdatable handles the optional updates. So, for the second step we need to add a WCF Data Service to our project. Right-click on the ODataTour project and select Add | New Item.... In the Add New Item dialog, you will find the WCF Data Service under Web. We'll name ours ODataService and click Add.
If you receive the following message, open the web.config file and change aspNetCompatibilityEnabled to true.
We are not quite done yet. We need to complete the WCF Data Service. Open ODataService.svc.cs and modify the code as follows: C# public class ODataService : DataService<NorthwindIBEntities> { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc. // Examples: // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
config.SetEntitySetAccessRule("Customers", EntitySetRights.All); config.SetEntitySetAccessRule("Orders", EntitySetRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } } VB Public Class ODataService Inherits DataService(Of NorthwindIBEntities) ' This method is called only once to initialize service-wide policies. Public Shared Sub InitializeService(ByVal config As _ DataServiceConfiguration) ' TODO: set rules to indicate which entity sets and ' service operations are visible, updatable, etc. ' Examples: ' config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); ' config.SetServiceOperationAccessRule("MyServiceOperation", ' ServiceOperationRights.All);
config.SetEntitySetAccessRule("Customers", EntitySetRights.All) config.SetEntitySetAccessRule("Orders", EntitySetRights.All) config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2 End Sub End Class Lets look at the above code. First, we specified our EntityManager as the data container for the service by providing it as the generic data type on the DataService<T> base class. Second, weve specified that both of our entity sets, customers and orders are visible and updatable. At this point we are ready to test our service. Right-click ODataService.svc and select View in Browser. If everything went well, you should receive the standard service definition response like this:
This response tells us that we have two entity sets as expected. So, lets see if we can actually query some data. Enter the following URL in the browsers address bar: http://localhost:9009/ODataService.svc/Customers Depending on the browser, youll have to view source in order to see the raw response. At this point, our OData service can be consumed by any client that supports OData.
Call server methods Remote server method call (RSMC) Last modified on June 01, 2011 15:03 Contents How to define a Remote Server Method Method signature How to call a Remote Server Method from the client How to secure a Remote Server Method Code samples Troubleshooting A Remote Server Method is a server-side method that is defined and deployed in an assembly on the server. This type of method can return any serializable object and can pass any serializable parameters. Typically server-side methods are used to batch several queries and other actions which might otherwise be too slow if called one-by-one from the client application.
How to define a Remote Server Method Here are the rules when defining a Remote Server Method. 1) The method signature must conform to the ServerMethodDelegate. C# public static Object ServerMethodDelegate(IPrincipal principal, EntityManager entityManager, params Object[] args); VB Public Shared Function ServerMethodDelegate(principal As IPrincipal,_ entityManager As EntityManager, ParamArray args As Object()) As Object End Function 2) You have to apply the [AllowRpc] attribute to the method. 3) All types passed as arguments and as the return value must be simple types or primitives or must meet the requirements for known types. Method signature Both the IPrincipal and EntityManager are provided to the method by DevForce, they are not passed from the client. IPrincipal - The IPrincipal passed to the method is the IPrincipal associated with the user of the client application, and is the same IPrincipal returned by the EntityManager.Login call. EntityManager - The EntityManager passed into the method is an empty server-side EntityManager constructed by DevForce specifically for the method call. This EntityManager does not need to be logged in, and may be used to issue queries and saves. params Object[] args - To pass any argument to be used inside the server method. How to call a Remote Server Method from the client You can invoke a server method from the client synchronously or asynchronously: InvokeServerMethod InvokeServerMethodAsync If you call the weakly typed overload (using strings for the type and method names), make sure that the type name is fully qualified like: MyNamespace.MyType, MyAssembly. If you call the strongly typed overload (using a delegate), make sure that the delegate is available on the client in an assembly with the same name as the server. How to secure a Remote Server Method Please see here for more details. Code samples 1) Define the server method. C# namespace DomainModel { public class ServerMethods {
[AllowRpc] public static int GetNumberOfOrders(IPrincipal principal, EntityManager entityManager, params Object[] args) {
// Query and get the count int count = entityManager.GetQuery<OrderSummary>() .Where(os => os.OrderDate > startDate && os.OrderDate < endDate) .Count();
return count; } } } VB Namespace DomainModel Public Class ServerMethods
<AllowRpc> _ Public Shared Function GetNumberOfOrders(principal As IPrincipal,_ entityManager As EntityManager, ParamArray args As Object()) As Integer
' Gather args Dim startDate As DateTime = DirectCast(args(0), DateTime) Dim endDate As DateTime = DirectCast(args(1), DateTime)
' Query and get the count Dim count As Integer = entityManager.GetQuery(Of OrderSummary)()_ .Where(Function(os) os.OrderDate > startDate AndAlso os.OrderDate < endDate)_ .Count()
Return count End Function End Class End Namespace 2) Call the server method passing fullTypeName and methodName. C# public int GetOrderCount(DateTime startDate, DateTime endDate) { string typeName = "DomainModel.ServerMethods, DomainModel"; string methodName = "GetNumberOfOrders"; return (int)_entityManager.InvokeServerMethod(typeName, methodName, startDate, endDate); } VB Public Function GetOrderCount(startDate As DateTime, endDate As DateTime) As Integer Dim typeName As String = "DomainModel.ServerMethods, DomainModel" Dim methodName As String = "GetNumberOfOrders" Return CInt(_entityManager.InvokeServerMethod(typeName, methodName, startDate, endDate)) End Function 3) Call the server method using ServerMethodDelegate. C# public int GetOrderCount(DateTime startDate, DateTime endDate) { var serverMethod = new ServerMethodDelegate(ServerMethods.GetNumberOfOrders); return (int)_entityManager.InvokeServerMethod(serverMethod, startDate, endDate); } VB Public Function GetOrderCount(ByVal startDate As Date, ByVal endDate As Date) As Integer Dim serverMethod = New ServerMethodDelegate(ServerMethods.GetNumberOfOrders) Return CInt(_entityManager.InvokeServerMethod(serverMethod, startDate, endDate)) End Function Troubleshooting So you've followed the setup information above, yet it's just not working. Maybe it looks like the method call is hanging, or maybe an indecipherable error message is returned. 1) Check the debug log for warning and informational messages. DevForce will log an informational message about every known type found, and every remote server method. Logging of the remote server methods is done lazily, as the methods are called, but if DevForce thinks the return type will be a problem it will write a warning to the log. 2) Add a try/catch around your sync call, or check the InvokeServerMethodOperation error returned from the asynchronous call. We try to intercept the underlying error (which is almost always a CommunicationException or SerializationException) and provide some more helpful information, but it's not always easy. Check the InnerException too. Exceptions with the InvokeServerMethod are almost always due to a problem with either the input or output types, so walk through the requirements above to ensure you haven't missed anything. 3) What if you're passing or returning a complex type, over which you have no control? Wrap the type in another simple class (or a struct as of release 6.0.7) and make sure that simple type meets all the serialization/known type requirements. Subscribe to a pub-sub service Last modified on June 21, 2011 16:13 Contents Define a push service o Method signature Subscribe to a push service o Pass arguments with the subscription Send notifications Receive notifications Configure the push service o Learn more Push notification allows client applications to subscribe to user-defined services on the EntityServer and to receive notifications from the server when an event of interest occurs. The push service runs on its own thread and can perform any processing desired, including starting additional threads or processes. The method is started upon first client subscription, and stopped after the last client unsubscribes. The push service is similar to the server-side method used by the InvokeServerMethod() call with the difference that while the latter is a request-response method call, the push service can run indefinitely and support any number of subscribing clients. The push feature may be an option if your invoked server method is timing out due to lengthy processing requirements.
Define a push service The class holding your service's entry method should be in an assembly deployed to the EntityServer. In addition, the method must meet the following criteria: 1) The method signature must conform to the ServerNotifyDelegate; C# public delegate void ServerNotifyDelegate(Guid serviceKey, INotificationManager notificationManager, EntityManager serverEntityManager); VB Public Delegate Sub ServerNotifyDelegate(ByVal serviceKey As Guid, _ ByVal notificationManager As INotificationManager, _ ByVal serverEntityManager As EntityManager) 2) The method must be decorated with the [AllowRpc] attribute and must be static; Here's an example: C# [AllowRpc] public static void MultiStepService(Guid serviceKey, INotificationManager notificationManager, EntityManager entityManager) { // ... } VB <AllowRpc> Public Shared Sub MultiStepService(ByVal serviceKey As Guid, _ ByVal notificationManager As INotificationManager, _ ByVal entityManager As EntityManager) ' ... End Sub Method signature All three parameters are provided to the method by DevForce. serviceKey - Server-assigned key which uniquely identifies this service. notificationManager - The INotificationManager used to obtain subscriber information and send notifications to client subscribers. serverEntityManager - A server-side EntityManager. Subscribe to a push service To subscribe to a push service, the client must call the EntityManagers RegisterCallback method: C# _entityManager.RegisterCallback( "DomainModel.LongRunningProcedure, DomainModel", /* Type name */ "MultiStepService", /* Method name */ LongRunningProcedureNotification, /* the callback */ "lrp"); /* user token */ VB _entityManager.RegisterCallback( _ "DomainModel.LongRunningProcedure, DomainModel", _ "MultiStepService", _ LongRunningProcedureNotification, _ "lrp") ' user token - the callback - Method name - Type name Once registered, the client remains subscribed to the service until either closing or calling CancelCallback. Pass arguments with the subscription You can also pass user-defined arguments to the push service when registering. Here's a sample passing a single parameter: C# _entityManager.RegisterCallback( "DomainModel.OrderWatcher, DomainModel", /* Type name */ "NewOrderService", /* Method name */ OrderPushCallback, /* the callback */ _owToken, /* user token */ employeeId); /* client argument(s) */ VB _entityManager.RegisterCallback( _ "DomainModel.OrderWatcher, DomainModel", _ "NewOrderService", _ OrderPushCallback, _ _owToken, _ employeeId) ' client argument(s) - user token - the callback - Method name - Type name You can pass any number of arguments, but all types must be serializable, defined on both the client and server, and known types. These arguments are available in the push service on the NotificationSubscriber. The service can retrieve all subscribers by calling the GetSubscribers method on the NotificationManager, passing the assigned service key: C# foreach (var subscriber in __notificationManager.GetSubscribers(__serviceKey)) { // Obtain arguments passed from client when registering. var args = subscriber.ClientArguments; // ... } VB For Each subscriber In __notificationManager.GetSubscribers(__serviceKey) ' Obtain arguments passed from client when registering. Dim args = subscriber.ClientArguments ' ... Next subscriber Send notifications When the service has a notification to send to one or more subscribers, it does so with the Send method on the NotificationManager. To broadcast to all subscribers: C# __notificationManager.Send(__serviceKey, "Some message here", "And another")); VB __notificationManager.Send(__serviceKey, "Some message here", "And another")) To send a notification to a specific subscriber, pass the NotificationSubscriber with the Send call: C# __notificationManager.Send(__serviceKey, subscriber, "One message", "And a second"); VB __notificationManager.Send(__serviceKey, subscriber, "One message", "And a second") You can pass any number of notification arguments to clients, and they can be of any type. As with the client arguments on the subscription, these types must be defined on both client and server, serializable, and identified as known types. Receive notifications On the client, the notifications will be received by the callback method which was specified when registering for the service. The callback method takes a SubscriptionOperation which will indicate if an error occurred or contain a NotificationData array containing the data sent from the server. C# private void OrderPushCallback(SubscriptionOperation op) { // Get the first argument and display. string msg = op.NotificationData[0].ToString(); MessageBox.Show(msg, "Received from OrderWatcher", MessageBoxButton.OK); VB Private Sub OrderPushCallback(ByVal op As SubscriptionOperation) ' Get the first argument and display. Dim msg As String = op.NotificationData(0).ToString() MessageBox.Show(msg, "Received from OrderWatcher", MessageBoxButton.OK) End Sub }
Configure the push service 1) Server To be able to use the push feature, you must first enable it on the server. You can use DevForce default settings by simply setting the following flag in the ideablade.configuration section of the servers config file: <notificationService enabled="true" /> You can also customize this configuration by defining the service endpoints in the system.serviceModel section of the config file, or implementing a custom ServiceHostEvents class. 2) Client a) WinClient applications You must specify the client port number in the ideablade.configuration section of the app.config file: <notificationService clientPort="9012"/> Choose an port number not already in use, and be sure to open the port in your firewall. If you need to customize the default configuration you can fully define the push endpoint in the serviceModel section of the configuration file, or implement a custom ServiceProxyEvents class. b) Silverlight In Silverlight applications, no configuration information in the app.config is provided. If you need to customize the default configuration you can fully define the push endpoint in the ServiceReferences.ClientConfig file, or implement a custom ServiceProxyEvents class. Learn more Push samples with additional configuration information Sample configuration files - The push endpoints and default bindings are shown.
Patterns and practices Last modified on March 23, 2011 15:38 You'll find a variety of patterns and practices, organized by associated client application, model, or controller. These downloadable examples should be used to help improve your understanding of how DevForce will improve your development experience. You may also find more specific code samples that address more specific problems helpful as well. Create subordinate entities via the Root entity Last modified on April 14, 2011 10:23 Contents Introduction to Aggregates Only the Root can create internal entities CreateLineItem example Only the Root entity of an Aggregate should be able to create other members of the Aggregate. The Root should have a Create method for each member. Member classes should be written such that only the root entity can create them. The Order and its LineItems is the canonical example of an Aggregate. This topic introduces the vocabulary of aggregates and explains how to implement the member Create methods.
Introduction to Aggregates Entities typically cluster into Aggregates in which one entity is the designated Root and the others are subordinate internal members. The root should control all access to the internal members. An Order entity and its LineItem entities constitute an "Order Aggregate". The Order is the Root of the aggregate; the LineItems are its internal, subordinate members. From the domain perspective, the Order and its LineItems are one thing, a conceptual Order, represented by the concrete root Order entity. The LineItems have no meaningful existence apart from their parent Order. You expect to get them from the Order; you wouldn't try to query for them independently of their parent order. A new, deleted, or changed LineItem is a modification of the Order Aggregate as surely as changing the Order date. You make this insight tangible by observing the following rule: when you would save a LineItem, you save its (modified) parent Order entity at the same time. They travel together in the same transactional package. Speaking of transaction, the root Order entity is the keeper of the "lock" or "concurrency property" for the Aggregate as a whole. Because the Order is always part of the save transaction and is always modified when any part of the aggregate is modified, concurrency conflicts are detected and resolved with the root Order entity. Only the Root can create internal entities When you adopt this way of thinking, you realize that LineItems cannot be created directly. Only the parent Order should be allowed to create them. It follows that: 1) The Order class should have a CreateLineItem method. 2) You should block attempts to create a OrderDetail directly by making all OrderDetail constructors internal. It also follows that you must ask the parent Order to delete or remove a member LineItem. You should not be able to delete a LineItem directly; you must go through its parent Order. CreateLineItem example
Replace unpredictable DateTime and Guid classes Last modified on June 22, 2011 13:58 Contents The problem SystemTime SystemGuid Make your own system utility classes Add system utility classes like SystemTime and SystemGuid to your application so you can test component behaviors that are otherwise sensitive to unpredictable values coming from the system environment (e.g., the current time or a new Guid).
The problem .NET environment functions typically return values that come from the operating system such as DateTime.Now and Guid.NewGuid(). These values are different everytime which complicates repeatable testing. You can control these values if you replace calls to the .NET functions with calls to similar functions in system utility classes that you wrote. These utility classes behave like the .NET functions under normal conditions but you can change their behavior during testing. SystemTime DateTimeis especially problematic for testers who want to validate logic that is time sensitive. Suppose you had business logic that expected something to happen three days from now. You certainly don't want a three-day test. You can write a SystemTime class such as the one included below. SystemTime has Now() and Today() methods that return a DateTime. If you do nothing, they behave exactly like DateTime.Now and DateTime.Today. Now everywhere in your application you always call SystemTime.Now() and SystemTime.Today() wherever you would have called DateTime.Now and DateTime.Today. If you do nothing, the behavior of your application is unchanged. During a test, however, you can replace the SystemTime.NowFn with a test-time function that returns any DateTime value you need. Remember when you are done to restore NowFn to the proper default behavior. A "ResetNowFn" method makes that easier for testers. C# /// <summary> /// Replacement for <see cref="DateTime"/> /// ** ALWAYS USE THIS INSTEAD OF CALLING DATETIME DIRECTLY /// </summary> /// <remarks> /// Covers the <see cref="DateTime"/> static methods so that /// test and development scenarios can reset the "Current" time /// to whatever they like. See <see cref="NowFn"/>. /// TODO: Extend to support UtcNow /// </remarks> public static class SystemTime {
/// <summary> /// Gets a <see cref="DateTime"/> that is set to the "current" date and time /// in the manner of <see cref="DateTime.Now"/>. /// See <see cref="NowFn"/>. /// </summary> public static DateTime Now() { return NowFn(); }
/// <summary> /// Gets "current" date /// in the manner of <see cref="DateTime.Today"/>. /// See <see cref="NowFn"/>. /// </summary> public static DateTime Today() { var now = Now(); return now-now.TimeOfDay; }
/// <summary> /// Function for getting the "current" date and time. /// Defaults to <see cref="DateTime.Now"/> but /// could be replaced in a test or development scenario. /// </summary> public static Func<DateTime> NowFn = () => DateTime.Now;
/// <summary> /// Reset <see cref="NowFn"/> to application default /// </summary> public static void ResetNowFn() { NowFn = () => DateTime.Now; } } VB ''' <summary> ''' Replacement for <see cref="DateTime"/> ''' ** ALWAYS USE THIS INSTEAD OF CALLING DATETIME DIRECTLY ''' </summary> ''' <remarks> ''' Covers the <see cref="DateTime"/> static methods so that ''' test and development scenarios can reset the "Current" time ''' to whatever they like. See <see cref="NowFn"/>. ''' TODO: Extend to support UtcNow ''' </remarks> Public NotInheritable Class SystemTime
''' <summary> ''' Gets a <see cref="DateTime"/> that is set to the "current" date and time ''' in the manner of <see cref="DateTime.Now"/>. ''' See <see cref="NowFn"/>. ''' </summary> Private Sub New() End Sub Public Shared Function Now() As Date Return NowFn() End Function
''' <summary> ''' Gets "current" date ''' in the manner of <see cref="DateTime.Today"/>. ''' See <see cref="NowFn"/>. ''' </summary> Public Shared Function Today() As Date Dim now = SystemTime.Now() Return now-now.TimeOfDay End Function
''' <summary> ''' Function for getting the "current" date and time. ''' Defaults to <see cref="DateTime.Now"/> but ''' could be replaced in a test or development scenario. ''' </summary> Public Shared NowFn As Func(Of Date) = Function() Date.Now
''' <summary> ''' Reset <see cref="NowFn"/> to application default ''' </summary> Public Shared Sub ResetNowFn() NowFn = Function() Date.Now End Sub End Class SystemGuid Guid.NewGuid() returns a new and deliberately unpredictable value every time. You may find yourself writing a test where you would like to control the value of that new Guid. You can write a SystemGuid class such as the one included below. SystemGuid has a NewGuid() method that return a Guid. If you do nothing, it behaves exactly like Guid.NewGuid(). Now everywhere in your application you always call SystemGuid.NewGuid() wherever you would have called Guid.NewGuid. If you do nothing, the behavior of your application is unchanged. During a test, however, you can replace the SystemGuid.NewGuidFn with a test-time function that returns any Guid value you need. Remember when you are done to restore NewGuidFn to the proper default behavior. A "ResetNewGuidFn" method makes that easier for testers. C# public static class SystemGuid { /// <summary> /// Gets a new <see cref="Guid"/> /// in the manner of <see cref="Guid.NewGuid"/>. /// See <see cref="NewGuidFn"/>. /// </summary> public static Guid NewGuid() { return NewGuidFn(); }
/// <summary> /// Function for getting a new <see cref="Guid"/>. /// Defaults to <see cref="Guid.NewGuid"/> but /// could be replaced in a test or development scenario. /// </summary> public static Func<Guid> NewGuidFn = Guid.NewGuid;
/// <summary> /// Reset <see cref="NewGuidFn"/> to application default /// </summary> public static void ResetNewGuidFn() { NewGuidFn = Guid.NewGuid; }
} VB Public NotInheritable Class SystemGuid ''' <summary> ''' Gets a new <see cref="Guid"/> ''' in the manner of <see cref="Guid.NewGuid"/>. ''' See <see cref="NewGuidFn"/>. ''' </summary> Private Sub New() End Sub Public Shared Function NewGuid() As Guid Return NewGuidFn() End Function
''' <summary> ''' Function for getting a new <see cref="Guid"/>. ''' Defaults to <see cref="Guid.NewGuid"/> but ''' could be replaced in a test or development scenario. ''' </summary> Public Shared NewGuidFn As Func(Of Guid) = Guid.NewGuid
''' <summary> ''' Reset <see cref="NewGuidFn"/> to application default ''' </summary> Public Shared Sub ResetNewGuidFn() NewGuidFn = Guid.NewGuid End Sub
End Class Make your own system utility classes Look for other places in your application where you call a .NET static function or property and consider following the pattern you see here.