using System; using System.Linq; using System.Linq.Expressions; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using DevExpress.Mvvm; using DevExpress.Mvvm.POCO; using DevExpress.Mvvm.DataAnnotations; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using DevExpress.DevAV.Common.Utils; using DevExpress.Mvvm.DataModel; namespace DevExpress.DevAV.Common.ViewModel { /// /// The base class for POCO view models exposing a collection of entities of the given type. /// This is a partial class that provides an extension point to add custom properties, commands and override methods without modifying the auto-generated code. /// /// A repository entity type. /// A projection entity type. /// A unit of work type. public abstract partial class EntitiesViewModel : EntitiesViewModelBase where TEntity : class where TProjection : class where TUnitOfWork : IUnitOfWork { /// /// Initializes a new instance of the EntitiesViewModel class. /// /// A factory used to create a unit of work instance. /// A function that returns a repository representing entities of the given type. /// A LINQ function used to customize a query for entities. The parameter, for example, can be used for sorting data and/or for projecting data to a custom type that does not match the repository entity type. protected EntitiesViewModel( IUnitOfWorkFactory unitOfWorkFactory, Func> getRepositoryFunc, Func, IQueryable> projection) : base(unitOfWorkFactory, getRepositoryFunc, projection) { } } /// /// The base class for a POCO view models exposing a collection of entities of the given type. /// It is not recommended to inherit directly from this class. Use the EntitiesViewModel class instead. /// /// A repository entity type. /// A projection entity type. /// A unit of work type. [POCOViewModel] public abstract class EntitiesViewModelBase : IEntitiesViewModel where TEntity : class where TProjection : class where TUnitOfWork : IUnitOfWork { #region inner classes protected interface IEntitiesChangeTracker { void RegisterMessageHandler(); void UnregisterMessageHandler(); } protected class EntitiesChangeTracker : IEntitiesChangeTracker { readonly EntitiesViewModelBase owner; ObservableCollection Entities { get { return owner.Entities; } } IRepository Repository { get { return (IRepository)owner.ReadOnlyRepository; } } public EntitiesChangeTracker(EntitiesViewModelBase owner) { this.owner = owner; } void IEntitiesChangeTracker.RegisterMessageHandler() { Messenger.Default.Register>(this, x => OnMessage(x)); } void IEntitiesChangeTracker.UnregisterMessageHandler() { Messenger.Default.Unregister(this); } public TProjection FindLocalProjectionByKey(TPrimaryKey primaryKey) { var primaryKeyEqualsExpression = RepositoryExtensions.GetProjectionPrimaryKeyEqualsExpression(Repository, primaryKey); return Entities.AsQueryable().FirstOrDefault(primaryKeyEqualsExpression); } public TProjection FindActualProjectionByKey(TPrimaryKey primaryKey) { var projectionEntity = Repository.FindActualProjectionByKey(owner.Projection, primaryKey); if(projectionEntity != null && ExpressionHelper.IsFitEntity(Repository.Find(primaryKey), owner.GetFilterExpression())) { owner.OnEntitiesLoaded(GetUnitOfWork(Repository), new TProjection[] { projectionEntity }); return projectionEntity; } return null; } void OnMessage(EntityMessage message) { if(!owner.IsLoaded) return; switch(message.MessageType) { case EntityMessageType.Added: OnEntityAdded(message.PrimaryKey); break; case EntityMessageType.Changed: OnEntityChanged(message.PrimaryKey); break; case EntityMessageType.Deleted: OnEntityDeleted(message.PrimaryKey); break; } } void OnEntityAdded(TPrimaryKey primaryKey) { var projectionEntity = FindActualProjectionByKey(primaryKey); if(projectionEntity != null) Entities.Add(projectionEntity); } void OnEntityChanged(TPrimaryKey primaryKey) { var existingProjectionEntity = FindLocalProjectionByKey(primaryKey); var projectionEntity = FindActualProjectionByKey(primaryKey); if(projectionEntity == null) { Entities.Remove(existingProjectionEntity); return; } if(existingProjectionEntity != null) { Entities[Entities.IndexOf(existingProjectionEntity)] = projectionEntity; owner.RestoreSelectedEntity(existingProjectionEntity, projectionEntity); return; } OnEntityAdded(primaryKey); } void OnEntityDeleted(TPrimaryKey primaryKey) { Entities.Remove(FindLocalProjectionByKey(primaryKey)); } } #endregion ObservableCollection entities = new ObservableCollection(); CancellationTokenSource loadCancellationTokenSource; protected readonly IUnitOfWorkFactory unitOfWorkFactory; protected readonly Func> getRepositoryFunc; protected Func, IQueryable> Projection { get; private set; } /// /// Initializes a new instance of the EntitiesViewModelBase class. /// /// A factory used to create a unit of work instance. /// A function that returns a repository representing entities of the given type. /// A LINQ function used to customize a query for entities. The parameter, for example, can be used for sorting data and/or for projecting data to a custom type that does not match the repository entity type. protected EntitiesViewModelBase( IUnitOfWorkFactory unitOfWorkFactory, Func> getRepositoryFunc, Func, IQueryable> projection ) { this.unitOfWorkFactory = unitOfWorkFactory; this.getRepositoryFunc = getRepositoryFunc; this.Projection = projection; this.ChangeTracker = CreateEntitiesChangeTracker(); if(!this.IsInDesignMode()) OnInitializeInRuntime(); } /// /// Used to check whether entities are currently being loaded in the background. The property can be used to show the progress indicator. /// public virtual bool IsLoading { get; protected set; } /// /// The collection of entities loaded from the unit of work. /// public ObservableCollection Entities { get { if(!IsLoaded) LoadEntities(false); return entities; } } protected IEntitiesChangeTracker ChangeTracker { get; private set; } protected IReadOnlyRepository ReadOnlyRepository { get; private set; } protected bool IsLoaded { get { return ReadOnlyRepository != null; } } protected void LoadEntities(bool forceLoad) { if(forceLoad) { if(loadCancellationTokenSource != null) loadCancellationTokenSource.Cancel(); } else if(IsLoading) { return; } loadCancellationTokenSource = LoadCore(); } void CancelLoading() { if(loadCancellationTokenSource != null) loadCancellationTokenSource.Cancel(); IsLoading = false; } CancellationTokenSource LoadCore() { IsLoading = true; var cancellationTokenSource = new CancellationTokenSource(); var selectedEntityCallback = GetSelectedEntityCallback(); Task.Factory.StartNew(() => { var repository = CreateReadOnlyRepository(); var entities = new ObservableCollection(repository.GetFilteredEntities(GetFilterExpression(), Projection)); OnEntitiesLoaded(GetUnitOfWork(repository), entities); return new Tuple, ObservableCollection>(repository, entities); }).ContinueWith(x => { if(!x.IsFaulted) { ReadOnlyRepository = x.Result.Item1; entities = x.Result.Item2; this.RaisePropertyChanged(y => y.Entities); OnEntitiesAssigned(selectedEntityCallback); } IsLoading = false; }, cancellationTokenSource.Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); return cancellationTokenSource; } static TUnitOfWork GetUnitOfWork(IReadOnlyRepository repository) { return (TUnitOfWork)repository.UnitOfWork; } protected virtual void OnEntitiesLoaded(TUnitOfWork unitOfWork, IEnumerable entities) { } protected virtual void OnEntitiesAssigned(Func getSelectedEntityCallback) { } protected virtual Func GetSelectedEntityCallback() { return null; } protected virtual void RestoreSelectedEntity(TProjection existingProjectionEntity, TProjection projectionEntity) { } protected virtual Expression> GetFilterExpression() { return null; } protected virtual void OnInitializeInRuntime() { if(ChangeTracker != null) ChangeTracker.RegisterMessageHandler(); } protected virtual void OnDestroy() { CancelLoading(); if(ChangeTracker != null) ChangeTracker.UnregisterMessageHandler(); } protected virtual void OnIsLoadingChanged() { } protected IReadOnlyRepository CreateReadOnlyRepository() { return getRepositoryFunc(CreateUnitOfWork()); } protected TUnitOfWork CreateUnitOfWork() { return unitOfWorkFactory.CreateUnitOfWork(); } protected virtual IEntitiesChangeTracker CreateEntitiesChangeTracker() { return null; } protected IDocumentOwner DocumentOwner { get; private set; } #region IDocumentContent object IDocumentContent.Title { get { return null; } } void IDocumentContent.OnClose(CancelEventArgs e) { } void IDocumentContent.OnDestroy() { OnDestroy(); } IDocumentOwner IDocumentContent.DocumentOwner { get { return DocumentOwner; } set { DocumentOwner = value; } } #endregion #region IEntitiesViewModel ObservableCollection IEntitiesViewModel.Entities { get { return Entities; } } bool IEntitiesViewModel.IsLoading { get { return IsLoading; } } #endregion } /// /// The base interface for view models exposing a collection of entities of the given type. /// /// An entity type. public interface IEntitiesViewModel : IDocumentContent where TEntity : class { /// /// The loaded collection of entities. /// ObservableCollection Entities { get; } /// /// Used to check whether entities are currently being loaded in the background. The property can be used to show the progress indicator. /// bool IsLoading { get; } } }