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; }
}
}