using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using DevExpress.Mvvm;
using DevExpress.Mvvm.POCO;
using DevExpress.Mvvm.DataAnnotations;
using DevExpress.DevAV.Common.Utils;
using DevExpress.Mvvm.DataModel;
namespace DevExpress.DevAV.Common.ViewModel {
///
/// The base class for a POCO view models exposing a colection of entities of a given type and CRUD operations against these entities.
/// This is a partial class that provides extension point to add custom properties, commands and override methods without modifying the auto-generated code.
///
/// An entity type.
/// A primary key value type.
/// A unit of work type.
public partial class CollectionViewModel : CollectionViewModel
where TEntity : class
where TUnitOfWork : IUnitOfWork {
///
/// Creates a new instance of CollectionViewModel as a POCO view model.
///
/// A factory used to create a unit of work instance.
/// A function that returns a repository representing entities of the given type.
/// An optional parameter that provides a LINQ function used to customize a query for entities. The parameter, for example, can be used for sorting data.
/// An optional parameter that provides a function to initialize a new entity. This parameter is used in the detail collection view models when creating a single object view model for a new entity.
/// An optional parameter that used to specify that the selected entity should not be managed by PeekCollectionViewModel.
public static CollectionViewModel CreateCollectionViewModel(
IUnitOfWorkFactory unitOfWorkFactory,
Func> getRepositoryFunc,
Func, IQueryable> projection = null,
Action newEntityInitializer = null,
bool ignoreSelectEntityMessage = false) {
return ViewModelSource.Create(() => new CollectionViewModel(unitOfWorkFactory, getRepositoryFunc, projection, newEntityInitializer, ignoreSelectEntityMessage));
}
///
/// Initializes a new instance of the CollectionViewModel class.
/// This constructor is declared protected to avoid an undesired instantiation of the CollectionViewModel type without the POCO proxy factory.
///
/// A factory used to create a unit of work instance.
/// A function that returns a repository representing entities of the given type.
/// An optional parameter that provides a LINQ function used to customize a query for entities. The parameter, for example, can be used for sorting data.
/// An optional parameter that provides a function to initialize a new entity. This parameter is used in the detail collection view models when creating a single object view model for a new entity.
/// An optional parameter that used to specify that the selected entity should not be managed by PeekCollectionViewModel.
protected CollectionViewModel(
IUnitOfWorkFactory unitOfWorkFactory,
Func> getRepositoryFunc,
Func, IQueryable> projection = null,
Action newEntityInitializer = null,
bool ignoreSelectEntityMessage = false
) : base(unitOfWorkFactory, getRepositoryFunc, projection, newEntityInitializer, ignoreSelectEntityMessage) {
}
}
///
/// The base class for a POCO view models exposing a collection of entities of a given type and CRUD operations against these entities.
/// This is a partial class that provides 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 primary key value type.
/// A unit of work type.
public partial class CollectionViewModel : CollectionViewModelBase
where TEntity : class
where TProjection : class
where TUnitOfWork : IUnitOfWork {
///
/// Creates a new instance of CollectionViewModel as a POCO view model.
///
/// 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.
/// An optional parameter that provides a function to initialize a new entity. This parameter is used in the detail collection view models when creating a single object view model for a new entity.
/// An optional parameter that used to specify that the selected entity should not be managed by PeekCollectionViewModel.
public static CollectionViewModel CreateProjectionCollectionViewModel(
IUnitOfWorkFactory unitOfWorkFactory,
Func> getRepositoryFunc,
Func, IQueryable> projection,
Action newEntityInitializer = null,
bool ignoreSelectEntityMessage = false) {
return ViewModelSource.Create(() => new CollectionViewModel(unitOfWorkFactory, getRepositoryFunc, projection, newEntityInitializer, ignoreSelectEntityMessage));
}
///
/// Initializes a new instance of the CollectionViewModel class.
/// This constructor is declared protected to avoid an undesired instantiation of the CollectionViewModel type without the POCO proxy factory.
///
/// 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.
/// An optional parameter that provides a function to initialize a new entity. This parameter is used in the detail collection view models when creating a single object view model for a new entity.
/// An optional parameter that used to specify that the selected entity should not be managed by PeekCollectionViewModel.
protected CollectionViewModel(
IUnitOfWorkFactory unitOfWorkFactory,
Func> getRepositoryFunc,
Func, IQueryable> projection,
Action newEntityInitializer = null,
bool ignoreSelectEntityMessage = false
) : base(unitOfWorkFactory, getRepositoryFunc, projection, newEntityInitializer, ignoreSelectEntityMessage) {
}
}
///
/// The base class for POCO view models exposing a collection of entities of a given type and CRUD operations against these entities.
/// It is not recommended to inherit directly from this class. Use the CollectionViewModel class instead.
///
/// A repository entity type.
/// A projection entity type.
/// A primary key value type.
/// A unit of work type.
public abstract class CollectionViewModelBase : ReadOnlyCollectionViewModel
where TEntity : class
where TProjection : class
where TUnitOfWork : IUnitOfWork {
EntitiesChangeTracker ChangeTrackerWithKey { get { return (EntitiesChangeTracker)ChangeTracker; } }
readonly Action newEntityInitializer;
IRepository Repository { get { return (IRepository)ReadOnlyRepository; } }
///
/// Initializes a new instance of the CollectionViewModelBase 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.
/// A function to initialize a new entity. This parameter is used in the detail collection view models when creating a single object view model for a new entity.
/// A parameter used to specify whether the selected entity should be managed by PeekCollectionViewModel.
protected CollectionViewModelBase(
IUnitOfWorkFactory unitOfWorkFactory,
Func> getRepositoryFunc,
Func, IQueryable> projection,
Action newEntityInitializer,
bool ignoreSelectEntityMessage
) : base(unitOfWorkFactory, getRepositoryFunc, projection) {
VerifyProjectionType();
this.newEntityInitializer = newEntityInitializer;
this.ignoreSelectEntityMessage = ignoreSelectEntityMessage;
if(!this.IsInDesignMode())
RegisterSelectEntityMessage();
}
///
/// Creates and shows a document that contains a single object view model for new entity.
/// Since CollectionViewModelBase is a POCO view model, an the instance of this class will also expose the NewCommand property that can be used as a binding source in views.
///
public virtual void New() {
GetDocumentManagerService().ShowNewEntityDocument(this, newEntityInitializer);
}
///
/// Creates and shows a document that contains a single object view model for the existing entity.
/// Since CollectionViewModelBase is a POCO view model, an the instance of this class will also expose the EditCommand property that can be used as a binding source in views.
///
/// Entity to edit.
public virtual void Edit(TProjection projectionEntity) {
if(Repository.IsDetached(projectionEntity))
return;
TPrimaryKey primaryKey = Repository.GetProjectionPrimaryKey(projectionEntity);
int index = Entities.IndexOf(projectionEntity);
projectionEntity = ChangeTrackerWithKey.FindActualProjectionByKey(primaryKey);
if(index >= 0) {
if(projectionEntity == null)
Entities.RemoveAt(index);
else
Entities[index] = projectionEntity;
}
if(projectionEntity == null) {
DestroyDocument(GetDocumentManagerService().FindEntityDocument(primaryKey));
return;
}
GetDocumentManagerService().ShowExistingEntityDocument(this, primaryKey);
}
///
/// Determines whether an entity can be edited.
/// Since CollectionViewModelBase is a POCO view model, this method will be used as a CanExecute callback for EditCommand.
///
/// An entity to edit.
public bool CanEdit(TProjection projectionEntity) {
return projectionEntity != null && !IsLoading;
}
///
/// Deletes a given entity from the repository and saves changes if confirmed by the user.
/// Since CollectionViewModelBase is a POCO view model, an the instance of this class will also expose the DeleteCommand property that can be used as a binding source in views.
///
/// An entity to edit.
public virtual void Delete(TProjection projectionEntity) {
if(MessageBoxService.ShowMessage(string.Format(CommonResources.Confirmation_Delete, typeof(TEntity).Name), CommonResources.Confirmation_Caption, MessageButton.YesNo) != MessageResult.Yes)
return;
try {
Entities.Remove(projectionEntity);
TPrimaryKey primaryKey = Repository.GetProjectionPrimaryKey(projectionEntity);
TEntity entity = Repository.Find(primaryKey);
if(entity != null) {
OnBeforeEntityDeleted(primaryKey, entity);
Repository.Remove(entity);
Repository.UnitOfWork.SaveChanges();
OnEntityDeleted(primaryKey, entity);
}
} catch (DbException e) {
Refresh();
MessageBoxService.ShowMessage(e.ErrorMessage, e.ErrorCaption, MessageButton.OK, MessageIcon.Error);
}
}
///
/// Determines whether an entity can be deleted.
/// Since CollectionViewModelBase is a POCO view model, this method will be used as a CanExecute callback for DeleteCommand.
///
/// An entity to edit.
public virtual bool CanDelete(TProjection projectionEntity) {
return projectionEntity != null && !IsLoading;
}
///
/// Saves the given entity.
/// Since CollectionViewModelBase is a POCO view model, the instance of this class will also expose the SaveCommand property that can be used as a binding source in views.
///
/// An entity to save.
[Display(AutoGenerateField = false)]
public virtual void Save(TProjection projectionEntity) {
TPrimaryKey primaryKey = Repository.GetProjectionPrimaryKey(projectionEntity);
TEntity entity = Repository.Find(primaryKey);
if(typeof(TProjection) != typeof(TEntity))
ApplyProjectionPropertiesToEntity(projectionEntity, entity);
try {
OnBeforeEntitySaved(primaryKey, entity);
Repository.UnitOfWork.SaveChanges();
OnEntitySaved(primaryKey, entity);
} catch (DbException e) {
MessageBoxService.ShowMessage(e.ErrorMessage, e.ErrorCaption, MessageButton.OK, MessageIcon.Error);
}
}
///
/// Determines whether entity local changes can be saved.
/// Since CollectionViewModelBase is a POCO view model, this method will be used as a CanExecute callback for SaveCommand.
///
/// An entity to save.
public virtual bool CanSave(TProjection projectionEntity) {
return projectionEntity != null && !IsLoading;
}
///
/// Notifies that SelectedEntity has been changed by raising the PropertyChanged event.
/// Since CollectionViewModelBase is a POCO view model, an the instance of this class will also expose the UpdateSelectedEntityCommand property that can be used as a binding source in views.
///
[Display(AutoGenerateField = false)]
public virtual void UpdateSelectedEntity() {
this.RaisePropertyChanged(x => x.SelectedEntity);
}
///
/// Closes the corresponding view.
/// Since CollectionViewModelBase is a POCO view model, an the instance of this class will also expose the CloseCommand property that can be used as a binding source in views.
///
[Display(AutoGenerateField = false)]
public void Close() {
if(DocumentOwner != null)
DocumentOwner.Close(this);
}
protected IMessageBoxService MessageBoxService { get { return this.GetRequiredService(); } }
protected virtual IDocumentManagerService GetDocumentManagerService() { return this.GetService(); }
protected virtual void OnBeforeEntityDeleted(TPrimaryKey primaryKey, TEntity entity) { }
protected virtual void OnEntityDeleted(TPrimaryKey primaryKey, TEntity entity) {
Messenger.Default.Send(new EntityMessage(primaryKey, EntityMessageType.Deleted));
}
protected override Func GetSelectedEntityCallback() {
var entity = SelectedEntity;
return () => FindLocalProjectionWithSameKey(entity);
}
TProjection FindLocalProjectionWithSameKey(TProjection projectionEntity) {
bool primaryKeyAvailable = projectionEntity != null && Repository.ProjectionHasPrimaryKey(projectionEntity);
return primaryKeyAvailable ? ChangeTrackerWithKey.FindLocalProjectionByKey(Repository.GetProjectionPrimaryKey(projectionEntity)) : null;
}
protected virtual void OnBeforeEntitySaved(TPrimaryKey primaryKey, TEntity entity) { }
protected virtual void OnEntitySaved(TPrimaryKey primaryKey, TEntity entity) {
Messenger.Default.Send(new EntityMessage(primaryKey, EntityMessageType.Changed));
}
protected virtual void ApplyProjectionPropertiesToEntity(TProjection projectionEntity, TEntity entity) {
throw new NotImplementedException("Override this method in the collection view model class and apply projection properties to the entity so that it can be correctly saved by unit of work.");
}
protected override void OnSelectedEntityChanged() {
base.OnSelectedEntityChanged();
UpdateCommands();
}
protected override void RestoreSelectedEntity(TProjection existingProjectionEntity, TProjection newProjectionEntity) {
base.RestoreSelectedEntity(existingProjectionEntity, newProjectionEntity);
if(ReferenceEquals(SelectedEntity, existingProjectionEntity))
SelectedEntity = newProjectionEntity;
}
protected override void OnIsLoadingChanged() {
base.OnIsLoadingChanged();
UpdateCommands();
if(!IsLoading)
RequestSelectedEntity();
}
void UpdateCommands() {
TProjection projectionEntity = null;
this.RaiseCanExecuteChanged(x => x.Edit(projectionEntity));
this.RaiseCanExecuteChanged(x => x.Delete(projectionEntity));
this.RaiseCanExecuteChanged(x => x.Save(projectionEntity));
}
protected void DestroyDocument(IDocument document) {
if(document != null)
document.Close();
}
protected IRepository CreateRepository() {
return (IRepository)CreateReadOnlyRepository();
}
protected override IEntitiesChangeTracker CreateEntitiesChangeTracker() {
return new EntitiesChangeTracker(this);
}
void VerifyProjectionType() {
//string primaryKeyPropertyName = CreateRepository().GetPrimaryKeyExpression.Name;
//if (TypeDescriptor.GetProperties(typeof(TProjection))[primaryKeyPropertyName] == null)
// throw new ArgumentException(string.Format("Projection type {0} should have primary key property {1}", typeof(TProjection).Name, primaryKeyPropertyName), "TProjection");
}
#region SelectEntityMessage
protected class SelectEntityMessage {
public SelectEntityMessage(TPrimaryKey primaryKey) {
PrimaryKey = primaryKey;
}
public TPrimaryKey PrimaryKey { get; private set; }
}
protected class SelectedEntityRequest { }
readonly bool ignoreSelectEntityMessage;
void RegisterSelectEntityMessage() {
if(!ignoreSelectEntityMessage)
Messenger.Default.Register(this, x => OnSelectEntityMessage(x));
}
void RequestSelectedEntity() {
if(!ignoreSelectEntityMessage)
Messenger.Default.Send(new SelectedEntityRequest());
}
void OnSelectEntityMessage(SelectEntityMessage message) {
if(!IsLoaded)
return;
var projectionEntity = ChangeTrackerWithKey.FindActualProjectionByKey(message.PrimaryKey);
if(projectionEntity == null) {
FilterExpression = null;
projectionEntity = ChangeTrackerWithKey.FindActualProjectionByKey(message.PrimaryKey);
}
SelectedEntity = projectionEntity;
}
#endregion
}
///
/// Provides the extension methods that are used to implement the IDocumentManagerService interface.
///
public static class DocumentManagerServiceExtensions {
///
/// Creates and shows a document containing a single object view model for the existing entity.
///
/// An instance of the IDocumentManager interface used to create and show the document.
/// An object that is passed to the view model of the created view.
/// An entity primary key.
public static void ShowExistingEntityDocument(this IDocumentManagerService documentManagerService, object parentViewModel, TPrimaryKey primaryKey) {
IDocument document = FindEntityDocument(documentManagerService, primaryKey) ?? CreateDocument(documentManagerService, primaryKey, parentViewModel);
if(document != null)
document.Show();
}
///
/// Creates and shows a document containing a single object view model for new entity.
///
/// An instance of the IDocumentManager interface used to create and show the document.
/// An object that is passed to the view model of the created view.
/// An optional parameter that provides a function that initializes a new entity.
public static void ShowNewEntityDocument(this IDocumentManagerService documentManagerService, object parentViewModel, Action newEntityInitializer = null) {
IDocument document = CreateDocument(documentManagerService, newEntityInitializer != null ? newEntityInitializer : x => DefaultEntityInitializer(x), parentViewModel);
if(document != null)
document.Show();
}
///
/// Searches for a document that contains a single object view model editing entity with a specified primary key.
///
/// An instance of the IDocumentManager interface used to find a document.
/// An entity primary key.
public static IDocument FindEntityDocument(this IDocumentManagerService documentManagerService, TPrimaryKey primaryKey) {
if(documentManagerService == null)
return null;
foreach(IDocument document in documentManagerService.Documents) {
ISingleObjectViewModel entityViewModel = document.Content as ISingleObjectViewModel;
if(entityViewModel != null && object.Equals(entityViewModel.PrimaryKey, primaryKey))
return document;
}
return null;
}
static void DefaultEntityInitializer(TEntity entity) { }
static IDocument CreateDocument(IDocumentManagerService documentManagerService, object parameter, object parentViewModel) {
if(documentManagerService == null)
return null;
return documentManagerService.CreateDocument(typeof(TEntity).Name + "View", parameter, parentViewModel);
}
}
}