devforce reference

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

Upload: abu-bakar-wan-ibrahim

Post on 22-Jun-2015

137 views

Category:

Documents


0 download

DESCRIPTION

this is reference for those who used Ideablade Devforce as their ORM in their DOTNET application.

TRANSCRIPT

EntityManager Last modified on March 23, 2011 22:14Contents 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 EntityManageris 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: MemberSummary 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 EntityServerErrorEvent raised when the EntityServer returns an exception for any reason TagGet 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. VerifierEngineGet 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: MemberSummary 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. DataSourceExtensionGet the string that identifies the data source targeted by this manager. You set the manager's DataSourceExtension when you construct it. DataSourceResolverGet a wealth of information about the data sources used by this manager. That environment is named in the manager's DataSourceExtension. EntityServiceOptionThe 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. IsClientWhether 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. MetadataStoreGet 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. MemberSummary ExecuteQuery Synchronously executes its IEntityQuery query argument, returning an untyped IEnumerable of entities. Not available in Silverlight. ExecuteQuerySynchronously executes its strongly typed IEntityQuery query argument, returning an IEnumerable 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 Executes asynchronously its strongly typed IEntityQuery query argument. , An IEnumerable 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 Executes asynchronously its strongly-typed IEntityQuery 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.FindEntitiesSearches the entity cache for entities of type "T" which also have an entitystate that matches one of the flag values in the EntityState argument. FindEntitiesSearches 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. FindEntitySearches 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. GetNullEntityGet 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. GetQueryReturns an EntityQuery that, if executed, would query for all entities of type "T". The statement, manager.GetEntity()), 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. MemberSummary 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. OnQueryingThe virtual method that a derived EntityManager can override to raise the Querying event. FetchingA 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. OnQueriedThe virtual method that a derived EntityManager can override to raise the Queried event. Finally, a few query-control members. MemberSummary 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 MemberSummaryHasChangesGet whether the manager's entity cache contains a changed entity. More specifically whether the entitystate of any cached entity is other than Unchanged. SaveChangesSave 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 SaveChangesAsyncSave 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. MemberSummary 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. OnSavingThe 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. OnSavedThe virtual method that a derived EntityManager can override to raise the Saved event. Finally, a few save-control members. MemberSummary DefaultSaveOptions Get and set the SaveOptions that regulate the save unless the SaveChanges caller submits substitute SaveOptions. ForceIdFixupImmediately 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. MemberSummaryIsEntityLoaded(key) Whether an entity with given EntityKey is in the manager's cache. CacheStateManagerGet 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 EntityGroupinside the cache that holds the cached instances of type "T". GetEntityGroupThe non-generic version of GetEntityGroup takes the entity type as a parameter. GetEntityGroups Get an array of all EntityGroupsin 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. MemberSummary AddEntitiesAdd entities to the cache as if they were new, Added entities that do not exist in the database and will be inserted if saved. AddEntityAdd 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. AttachEntitiesAttach entities to the cache as if they were pre-existing, Unchanged entities that might have been queried from the database. AttachEntityAttach an entity to the cache as if it were a pre-existing, Unchanged entity that might have been queried from the database. ClearClear the cache, emptying it of all entities. Also clears the QueryCache described in the Query section above. ClearedEvent 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). ImportEntitiesImport 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. MemberSummary 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. CreateEntityCreate new entity of type "T"; the entity remains detached although the entity retains the inaccessible memory of the manager that created it. CreateEntityCreate 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. EntityChangedEvent 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.MemberSummary 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. LoginAsyncThe asynchronous variant of Login, available on all client technologies. LogoutLogout (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. LogoutAsyncThe asynchronous variant of Logout, available on all client technologies. PrincipalGet 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 UserBaseclass. 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: MemberSummary ConnectConnect to the server explicitly and synchronously. Not available in Silverlight because synchronous. ConnectAsyncConnect 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. MemberSummary InvokeServerMethodCall 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. InvokeServerMethodAsyncThe 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). MemberSummary RegisterCallbackRegister 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. CancelCallbackUnsubscribe to the notification service by cancelling the callback via its associated userToken Create an EntityManager Last modified on April 20, 2011 10:29Construct 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 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 documentationfor 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 Customers { get { return new IbEm.EntityQuery("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().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 queries2.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 EntityManagerautomatically 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 Disconnectmethod.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 IsConnectedproperty. The IsConnectedproperty 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 EntityServerErrorevent.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 Connector ConnectAsyncmethods; 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

Partial Public Class NorthwindEntities Inherits IbEm.EntityManager End Class

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(); // "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() ' "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

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 LinkForAuthenticationmethod. 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 EntityManagermaintains 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 AddEntitymethod. 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 AttachEntitymethod. An attached entity is regarded as an existing, unmodified entity like one that has been queried. 3.By importing it with the manager's ImportEntitiesmethod. 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 Clearmethod 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() .Where(c => c.CompanyName == "Acme") // cust1's name .FirstOrDefault(); Assert.AreSame(cust1, foundCust); VB Dim foundCust = manager _ .FindEntities(EntityState.AllButDetached) _ .OfType(Of Customer)() _ .Where(Function(c) c.CompanyName = "Acme") _ .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(); 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(); int cacheChangeCount = 0; manager.EntityChanged += (s, e) => cacheChanges.Add(e.Action); // Listen for changes to customers in cache var custChanges = new List(); int custChangeCount = 0; var custGrp = manager.GetEntityGroup(); custGrp.EntityChanged += (s, e) => custChanges.Add(e.Action); // Customer changes var cust = new Customer(); manager.AddEntity(cust); cacheChangeCount++; custChangeCount++; cust.CompanyName = "Acme"; cacheChangeCount++; custChangeCount++; cust.CompanyName = "Beta"; cacheChangeCount++; custChangeCount++; cust.EntityAspect.AcceptChanges(); cacheChangeCount++; custChangeCount++; cust.EntityAspect.Delete();cacheChangeCount++; custChangeCount++; // 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 cust.CompanyName = "Acme" cacheChangeCount += 1 custChangeCount += 1 cust.CompanyName = "Beta" cacheChangeCount += 1 custChangeCount += 1 cust.EntityAspect.AcceptChanges() cacheChangeCount += 1 custChangeCount += 1 cust.EntityAspect.Delete() 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 returnsVB 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: ConnectAsyncLoginAsyncLogoutAsyncExecuteQueryAsyncExecuteQueryAsyncSaveChangesAsyncRefetchEntitiesAsyncInvokeServerMethodAsyncYou 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 arriveVB ' 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 ExecuteQueryAsync( IEntityQuery query, Action 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 BaseOperationclass 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 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(); manager.ExecuteQueryAsync(query, CustomerQueryCallback); } void CustomerQueryCallback(EntityQueryOperation 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 callsin 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(); 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 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. BaseOperationreveals the members in common. MemberDescription CanCancelGet if this operation is cancellable. Cancel()Try to cancel the operation. It may (still) be cancellable. CancelledGet if the operation was canceled before it completed. CompletedRaised when the operation has completed, successfully or otherwise. A synonym for IsCompleted. CompletedSuccessfullyGet if the operation completed without error and without having been cancelled. CompletedSynchronouslyGet 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. ErrorIf 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. IsCompletedGet if the operation finished, successfully or otherwise. A synonym for Completed. IsErrorHandledGet 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. UserStateGet 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 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 Coroutineclass. 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); } 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 LoadTopCustomersCoroutine() { var userId = CurrentUser.UserId; var topCustOperation = Manager.Customers .Where(tc => tc.SalesRepID == userId) .Select(tc.CustomerIDs); // project just the CustomerIDs .ExecuteAsync()); yield return topCustOperation; // SUSPEND IList salesRepsTopCustIDs = topCustOperation.Results; 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 Corout