Example T884361
Visible to All Users

XAF - How to Implement CRUD Operations for Non-Persistent Objects Stored Remotely

XAF - How to Implement CRUD Operations for Non-Persistent Objects Stored Remotely

This example demonstrates an implementation of editable non-persistent objects that represent data stored remotely and separately from the main XAF application database. You can create, modify, and delete these non-persistent objects. The changes are persisted in the external storage. The built-in IsNewObject function is used in the Appearance rule criterion that disables the key property editor after a non-persistent object is saved.

Implementation Details

This example uses NonPersistentObjectSpace members.

Non-persistent objects are kept in an object map. XAF uses event handlers to manage these objects.

  1. To look up non-persistent objects and add them to the object map:
  2. To clear the object map:
    • Reloaded
      Subsequent object queries trigger the creation of new non-persistent object instances.
  3. To reload the state of an existing object from storage:
  4. To process all object changes and pass them to storage:

The NonPersistentObjectSpace.AutoSetModifiedOnObjectChange property is set to true to automatically mark non-persistent objects as modified when XAF raises the INotifyPropertyChanged.PropertyChanged event.

List<T> objects store non-persistent data.

Common Components

The following classes are used to provide a common functionality for all non-persistent objects used in the demo.

  • TransientNonPersistentObjectAdapter
    The adapter for transient (short-living) non-persistent business objects. Such objects exist only during the lifespan of their object space. A new adapter instance is created for each non-persistent object space. It subscribes to object space events to manage a subset of object types in a common manner. It also maintains an identity map (ObjectMap) for NonPersistentObjectSpace.
  • NonPersistentStorageBase
    Descendants of this class know how to create object instances and transfer data between objects and the storage. They know nothing about the adapter. They also use the identity map to avoid creating duplicated objects.

Files to Review

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

This example demonstrates an implementation of editable non-persistent objects that represent data stored remotely and separately from the main XAF application database. You can create, modify, and delete these non-persistent objects. The changes are persisted in the external storage. The built-in IsNewObject function is used in the Appearance rule criterion that disables the key property editor after a non-persistent object is saved.

Implementation Details

This example uses NonPersistentObjectSpace members.

Non-persistent objects are kept in an object map. XAF uses event handlers to manage these objects.

  1. To look up non-persistent objects and add them to the object map:
  2. To clear the object map:
    • Reloaded
      Subsequent object queries trigger the creation of new non-persistent object instances.
  3. To reload the state of an existing object from storage:
  4. To process all object changes and pass them to storage:

The NonPersistentObjectSpace.AutoSetModifiedOnObjectChange property is set to true to automatically mark non-persistent objects as modified when XAF raises the INotifyPropertyChanged.PropertyChanged event.

List<T> objects store non-persistent data.

Common Components

The following classes are used to provide a common functionality for all non-persistent objects used in the demo.

  • TransientNonPersistentObjectAdapter
    The adapter for transient (short-living) non-persistent business objects. Such objects exist only during the lifespan of their object space. A new adapter instance is created for each non-persistent object space. It subscribes to object space events to manage a subset of object types in a common manner. It also maintains an identity map (ObjectMap) for NonPersistentObjectSpace.
  • NonPersistentStorageBase
    Descendants of this class know how to create object instances and transfer data between objects and the storage. They know nothing about the adapter. They also use the identity map to avoid creating duplicated objects.

Files to Review

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

EFCore/NonPersistentObjectsDemo.Module/BusinessObjects/Account.cs
C#
using System.ComponentModel; using DevExpress.ExpressApp; using DevExpress.Persistent.Base; using DevExpress.Persistent.Validation; namespace NonPersistentObjectsDemo.Module.BusinessObjects { [DefaultClassOptions] [DefaultListViewOptions(true, NewItemRowPosition.Top)] [DefaultProperty("PublicName")] [DevExpress.ExpressApp.DC.DomainComponent] public class Account : NonPersistentObjectImpl { private string _myKey; [DevExpress.ExpressApp.ConditionalAppearance.Appearance("", Enabled = false, Criteria = "Not IsNewObject(This)")] [RuleRequiredField] [DevExpress.ExpressApp.Data.Key] public string MyKey { get { return _myKey; } set { _myKey = value; } } private string publicName; public string PublicName { get { return publicName; } set { SetPropertyValue(ref publicName, value); } } } }
EFCore/NonPersistentObjectsDemo.Module/ServiceClasses/PostOfficeStorage.cs
C#
using DevExpress.Data.Filtering; using DevExpress.Xpo.DB; using DevExpress.XtraRichEdit.Export.WordML; using NonPersistentObjectsDemo.Module.BusinessObjects; using NonPersistentObjectsDemo.Module.Stubs; using System; using System.Collections; using System.Linq; namespace NonPersistentObjectsDemo.Module.ServiceClasses { public class PostOfficeStorage : NonPersistentStorageBase { public PostOfficeStorage() { Storage = new List<AccountStub>(); CreateDemoData(Storage); } static void CreateDemoData(List<AccountStub> list) { var idsAccount = new List<string>(); for(int i = 0; i < 10; i++) { var accStub = new AccountStub(); accStub.MyKey = "key" + i; accStub.MyName = "Name" + i; list.Add(accStub); } } List<AccountStub> Storage; public override object GetObjectByKey(Type objectType, object key) { if(key == null) { throw new ArgumentNullException(nameof(key)); } var stub = Storage.Where(x => x.MyKey == (string)key).FirstOrDefault(); if(stub != null) { var acc = new Account(); acc.MyKey = stub.MyKey; acc.PublicName = stub.MyName; return acc; } throw new NotImplementedException(); } public override IEnumerable GetObjects(Type objectType, CriteriaOperator criteria, IList<DevExpress.Xpo.SortProperty> sorting) { var lst = new List<Account>(); foreach(var stub in Storage) { var acc = new Account(); acc.MyKey = stub.MyKey; acc.PublicName = stub.MyName; lst.Add(acc); } return lst; } public override void SaveObjects(ICollection toInsert, ICollection toUpdate, ICollection toDelete) { foreach(Account obj in toInsert) { var stub = new AccountStub(); stub.MyKey = obj.MyKey; stub.MyName = obj.PublicName; Storage.Add(stub); } foreach(Account obj in toUpdate) { var stub = Storage.Where(x => x.MyKey == obj.MyKey).FirstOrDefault(); if(stub != null) { stub.MyName = obj.PublicName; } } foreach(Account obj in toDelete) { var stub = Storage.Where(x => x.MyKey == obj.MyKey).FirstOrDefault(); if(stub != null) { Storage.Remove(stub); } } } } }
EFCore/NonPersistentObjectsDemo.Module/ServiceClasses/TransientNonPersistentObjectAdapter.cs
C#
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using DevExpress.Data.Filtering; using DevExpress.ExpressApp; namespace NonPersistentObjectsDemo.Module { class TransientNonPersistentObjectAdapter { private NonPersistentObjectSpace objectSpace; private NonPersistentStorageBase storage; private ObjectMap objectMap; public TransientNonPersistentObjectAdapter(NonPersistentObjectSpace objectSpace, ObjectMap objectMap, NonPersistentStorageBase _storage) { this.objectSpace = objectSpace; this.storage = _storage; this.objectMap = objectMap; objectSpace.ObjectsGetting += ObjectSpace_ObjectsGetting; objectSpace.ObjectGetting += ObjectSpace_ObjectGetting; objectSpace.ObjectByKeyGetting += ObjectSpace_ObjectByKeyGetting; objectSpace.Reloaded += ObjectSpace_Reloaded; objectSpace.CustomCommitChanges += ObjectSpace_CustomCommitChanges; objectSpace.ObjectReloading += ObjectSpace_ObjectReloading; } private void ObjectSpace_ObjectReloading(object sender, ObjectGettingEventArgs e) { if(e.SourceObject != null && objectMap.IsKnown(e.SourceObject.GetType())) { if(IsNewObject(e.SourceObject)) { e.TargetObject = null; } else { var key = objectSpace.GetKeyValue(e.SourceObject); e.TargetObject = storage.GetObjectByKey(e.SourceObject.GetType(), key); } } } private void ObjectSpace_CustomCommitChanges(object sender, HandledEventArgs e) { var toSave = objectSpace.GetObjectsToSave(false); var toInsert = new List<object>(); var toUpdate = new List<object>(); foreach(var obj in toSave) { if(objectSpace.IsNewObject(obj)) { toInsert.Add(obj); } else { toUpdate.Add(obj); } } var toDelete = objectSpace.GetObjectsToDelete(false); if(toInsert.Count != 0 || toUpdate.Count != 0 || toDelete.Count != 0) { storage.SaveObjects(toInsert, toUpdate, toDelete); } //e.Handled = false;// !!! } private void ObjectSpace_Reloaded(object sender, EventArgs e) { objectMap.Clear(); } private void ObjectSpace_ObjectByKeyGetting(object sender, ObjectByKeyGettingEventArgs e) { if(e.Key != null && objectMap.IsKnown(e.ObjectType)) { Object obj = objectMap.Get(e.ObjectType, e.Key); if(obj == null) { obj = storage.GetObjectByKey(e.ObjectType, e.Key); if(obj!= null && !objectMap.Contains(obj)) { objectMap.Add(e.ObjectType, e.Key, obj); } } if(obj!= null) { e.Object = obj; } } } private void ObjectSpace_ObjectGetting(object sender, ObjectGettingEventArgs e) { if(e.SourceObject != null && objectMap.IsKnown(e.SourceObject.GetType())) { var link = (IObjectSpaceLink)e.SourceObject; if(objectSpace.Equals(link.ObjectSpace) && (objectMap.Contains(e.SourceObject) || IsNewObject(e.SourceObject))) { e.TargetObject = e.SourceObject; } else { var key = objectSpace.GetKeyValue(e.SourceObject); e.TargetObject = objectSpace.GetObjectByKey(e.SourceObject.GetType(), key); } } } private void ObjectSpace_ObjectsGetting(object sender, ObjectsGettingEventArgs e) { if(objectMap.IsKnown(e.ObjectType)) { var collection = new DynamicCollection(objectSpace, e.ObjectType, e.Criteria, e.Sorting, e.InTransaction); collection.FetchObjects += DynamicCollection_FetchObjects; e.Objects = collection; } } private static bool IsNewObject(object obj) { var sourceObjectSpace = BaseObjectSpace.FindObjectSpaceByObject(obj); return sourceObjectSpace == null ? false : sourceObjectSpace.IsNewObject(obj); } private IEnumerable GetList(Type objectType, CriteriaOperator criteria, IList<DevExpress.Xpo.SortProperty> sorting) { return storage.GetObjects(objectType, criteria, sorting); } private void DynamicCollection_FetchObjects(object sender, FetchObjectsEventArgs e) { e.Objects = GetList(e.ObjectType, e.Criteria, e.Sorting); e.ShapeData = true; } } public class ObjectMap { private Dictionary<Type, Dictionary<Object, Object>> typeMap; private NonPersistentObjectSpace objectSpace; public ObjectMap(NonPersistentObjectSpace objectSpace, params Type[] types) { this.objectSpace = objectSpace; this.typeMap = new Dictionary<Type, Dictionary<object, object>>(); foreach(var type in types) { typeMap.Add(type, new Dictionary<object, object>()); } } public bool IsKnown(Type type) { return typeMap.ContainsKey(type); } public bool Contains(Object obj) { Dictionary<Object, Object> objectMap; if(typeMap.TryGetValue(obj.GetType(), out objectMap)) { return objectMap.ContainsValue(obj); } return false; } public void Clear() { foreach(var kv in typeMap) { kv.Value.Clear(); } } public T Get<T>(Object key) { return (T)Get(typeof(T), key); } public Object Get(Type type, Object key) { Dictionary<Object, Object> objectMap; if(typeMap.TryGetValue(type, out objectMap)) { Object obj; if(objectMap.TryGetValue(key, out obj)) { return obj; } } return null; } public void Add(Type type, Object key, Object obj) { Dictionary<Object, Object> objectMap; if(typeMap.TryGetValue(type, out objectMap)) { objectMap.Add(key, obj); } } public void Accept(Object obj) { objectSpace.GetObject(obj); } } }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.