Reputation: 2684
I'm using this framework: URF and Caliburn Micro to create a business WPF application.
This is the code for the CM bootstrapper:
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer container;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
container = new SimpleContainer();
container.Singleton<IWindowManager, WindowManager>();
container.Singleton<IEventAggregator, EventAggregator>();
container.PerRequest<IShell, ShellViewModel>();
container.AllTypesOf<ITabItem>(Assembly.GetExecutingAssembly());
container.PerRequest<IDataContextAsync, AuraContext>();
container.PerRequest<IUnitOfWorkAsync, UnitOfWork>();
container.PerRequest<IRepositoryAsync<Audit>, Repository<Audit>>();
container.PerRequest<IAuditService, AuditService>();
}
protected override object GetInstance(Type service, string key)
{
var instance = container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException(String.Format("Could not locate any instances of type {0}", service.Name));
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return container.GetAllInstances(serviceType);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<IShell>();
}
}
The ShellViewModel.cs code:
public class ShellViewModel: Conductor<ITabItem>.Collection.OneActive, IShell
{
private readonly IWindowManager _windowManager;
[ImportingConstructor]
public ShellViewModel(IWindowManager windowManager, IEnumerable<ITabItem> tabItems)
{
DisplayName = "Aura";
_windowManager = windowManager;
Items.AddRange(tabItems.Where(t => t.IsEnabled).OrderBy(t => t.DisplayOrder));
}
}
The ShellView.xaml markup:
<UserControl x:Class="Aura.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Aura"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600" MinWidth="800" MinHeight="600">
<TabControl x:Name="Items" Margin="3">
</UserControl>
This is some Repository:
public static class AuditRepository
{
public static async Task<Audit> GetCurrentAudit(this IRepositoryAsync<Audit> repository)
{
var audits = await repository
.Query(a => a.BeginDate.Year == DateTime.Now.Year)
.Include()
.SelectAsync(); ;
return audits.FirstOrDefault();
}
public static IEnumerable<Reminder> GetRemindersForAudit(this IRepositoryAsync<Audit> repository, int auditId)
{
var audits = repository.GetRepository<Audit>().Queryable();
var phases = repository.GetRepository<Phase>().Queryable();
var reminders = repository.GetRepository<Reminder>().Queryable();
var query = from audit in audits
where audit.Id == auditId
join phase in phases on audit.Id equals phase.AuditId
join reminder in reminders on phase.Id equals reminder.PhaseId
select reminder;
return query.AsEnumerable();
}
}
And its Service:
public interface IAuditService: IService<Audit>
{
Task<Audit> GetCurrentAudit();
}
public class AuditService: Service<Audit>, IAuditService
{
private readonly IRepositoryAsync<Audit> _repository;
public AuditService(IRepositoryAsync<Audit> repository)
:base(repository)
{
_repository = repository;
}
public async Task<Audit> GetCurrentAudit()
{
return await _repository.GetCurrentAudit();
}
public override void Delete(Audit entity)
{
// business logic here
base.Delete(entity);
}
public override void Update(Audit entity)
{
// business logic here
base.Update(entity);
}
public override void Insert(Audit entity)
{
// business logic here
base.Insert(entity);
}
}
This is my ViewModels constructor:
[ImportingConstructor]
public AdminViewModel(
IWindowManager windowManager,
IEventAggregator eventAggregator,
IUnitOfWorkAsync unitOfWorkAsync,
IAuditService auditService)
{
_windowManager = windowManager;
_eventAggregator = eventAggregator;
_unitOfWorkAsync = unitOfWorkAsync;
_auditService = auditService
}
And the implementation in that ViewModel that's giving me issues:
try
{
//var audits = await _unitOfWorkAsync.RepositoryAsync<Audit>().Query().SelectAsync();
//Audits = new ObservableCollection<Audit>(audits);
SelectedAudit = await _auditService.GetCurrentAudit();
//AuditHistoryHeader = String.Format(Constants.ADMINVIEW_AUDITHISTORYHEADER, Audits.Count);
SelectedAudit.ObjectState = ObjectState.Deleted;
_auditService.Delete(SelectedAudit);
_unitOfWorkAsync.SaveChanges();
var audit = _unitOfWorkAsync.Repository<Audit>().Query().Select().FirstOrDefault();
_unitOfWorkAsync.Repository<Audit>().Delete(audit);
_unitOfWorkAsync.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Something I'm not sure of in the URF UnitOfWork.cs file:
public IRepository<TEntity> Repository<TEntity>() where TEntity : Entity, IEntity
{
try
{
if (ServiceLocator.IsLocationProviderSet)
//if (ServiceLocator.Current != null)
//{
return ServiceLocator.Current.GetInstance<IRepository<TEntity>>();
//}
}
catch (Exception)
{
}
return RepositoryAsync<TEntity>();
//return IoC.Get<IRepositoryAsync<TEntity>>();
}
The problem is that the only way to persist CRUD operations to the database is with the _unitOfWorkAsync.Repository() object and not using the service.
That does not fail but no changes in the DB.. I'm a bit unsure about the ServiceLocator
used in URF and the SimpleContainer
from Caliburn Micro and how they (should) work together. I'm also unsure about the LifeTime of the containers objects in the Bootstrapper.cs
file.
I'm just starting to understand the DI pattern but I think this is what is giving me the issue I'm having..
If someone already did something like this or sees what the problem is, please let me know. If you like to see more code, please comment below.
EDIT:
I've tried the following but the data is not being deleted..
try
{
SelectedAudit = await _auditService.GetCurrentAudit();
SelectedAudit.ObjectState = ObjectState.Deleted;
_unitOfWorkAsync.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
If I step into the UnitOfWork.cs SaveChanges
and look into the IDataContextAsync _dataContext
Audits dbSet the Audit has ObjectState Unchanged
. So the deleted Audit is not part of that context?!
Upvotes: 1
Views: 503
Reputation: 4041
I can't speak for your DI setup, as I've never used SimpleContainer (I'm using URF with Autofac). The code in URF you are unsure of is fine. Works great with Autofac and Ninject so I'm guessing SimpleContainer will be similar. But one problem I see is in the following lines of code:
SelectedAudit = await _auditService.GetCurrentAudit();
//AuditHistoryHeader = String.Format(Constants.ADMINVIEW_AUDITHISTORYHEADER, Audits.Count);
SelectedAudit.ObjectState = ObjectState.Deleted;
_auditService.Delete(SelectedAudit); // <-- This is a problem
_unitOfWorkAsync.SaveChanges();
First, you get the SelectedAudit. SelectedAudit now being tracked by entity framework. You then set the state of SelectedAudit to deleted. So far so good. All you have to do now is call Savechanges. SelectedAudit is already attached to you entity framework context and marking it's state as deleted is enough for entity framework to know to delete it. Calling Delete from your service will try attach the SelectedAudit to the context again. This will either throw an exception (most likely) or cause undesired behaviour. If you remove the line
_auditService.Delete(SelectedAudit);
it should work. Note this is the same for updates to an entity. Get the entity, make changes to it then call SaveChanges WITHOUT calling your service update method.
You should only uses the update/delete methods if you DON'T get the entity first within the same context.
As for lifetime management, default URF uses PerRequest for IDataContextAsync, IUnitOfWorkAsync and INorthwindStoredProcedures. The rest all use TransientLifetime. Stick with that and change if you need to.
EXTRA INFO I've modified my service to accept viewmodels instead of entities. This is the service I use. I like this better as it keeps better separation (IMO) between the DAL and Web layers.
public interface IService<TModel>
{
TModel Find(params object[] keyValues);
Task<TModel> Insert(TModel model);
IEnumerable<TModel> InsertRange(IEnumerable<TModel> models);
Task<TModel> Update(TModel model);
void Delete(object id);
void Delete(TModel model);
Task<TModel> FindAsync(params object[] keyValues);
Task<TModel> FindAsync(CancellationToken cancellationToken, params object[] keyValues);
Task<bool> DeleteAsync(params object[] keyValues);
Task<bool> DeleteAsync(CancellationToken cancellationToken, params object[] keyValues);
}
public abstract class Service<TModel, TEntity> : IService<TModel> where TEntity : class, IObjectState
{
#region Private Fields
private readonly IRepositoryAsync<TEntity> _repository;
private readonly IUnitOfWorkAsync _unitOfWork;
private readonly IMapper _mapper;
#endregion Private Fields
#region Constructor
protected Service(IRepositoryAsync<TEntity> repository, IUnitOfWorkAsync unitOfWork, IMapper mapper)
{
_repository = repository;
_unitOfWork = unitOfWork;
_mapper = mapper;
}
#endregion Constructor
public void Delete(TModel model)
{
_unitOfWork.RepositoryAsync<TEntity>().Delete(_mapper.Map<TEntity>(model));
_unitOfWork.SaveChanges();
}
public void Delete(object id)
{
_unitOfWork.RepositoryAsync<TEntity>().Delete(id);
_unitOfWork.SaveChanges();
}
public async Task<bool> DeleteAsync(params object[] keyValues)
{
return await DeleteAsync(CancellationToken.None, keyValues);
}
public async Task<bool> DeleteAsync(CancellationToken cancellationToken, params object[] keyValues)
{
var result = await _unitOfWork.RepositoryAsync<TEntity>().DeleteAsync(cancellationToken, keyValues);
_unitOfWork.SaveChanges();
return result;
}
public TModel Find(params object[] keyValues)
{
return _mapper.Map<TModel>(_repository.Find(keyValues));
}
public async Task<TModel> FindAsync(params object[] keyValues)
{
var entity = await _repository.FindAsync(keyValues);
return _mapper.Map<TModel>(entity);
}
public async Task<TModel> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
var entity = await _repository.FindAsync(cancellationToken, keyValues);
return _mapper.Map<TModel>(entity);
}
public async Task<TModel> Insert(TModel model)
{
var entity = _unitOfWork.RepositoryAsync<TEntity>().Insert(_mapper.Map<TEntity>(model));
await _unitOfWork.SaveChangesAsync();
return _mapper.Map<TModel>(entity);
}
public IEnumerable<TModel> InsertRange(IEnumerable<TModel> models)
{
var entities = _unitOfWork.RepositoryAsync<TEntity>().InsertRange(_mapper.Map<IEnumerable<TEntity>>(models));
_unitOfWork.SaveChanges();
return _mapper.Map<IEnumerable<TModel>>(entities);
}
public async Task<TModel> Update(TModel model)
{
var entity = _unitOfWork.RepositoryAsync<TEntity>().Update(_mapper.Map<TEntity>(model));
await _unitOfWork.SaveChangesAsync();
return _mapper.Map<TModel>(entity);
}
public async Task<TModel> UpdateFieldsOnly(TModel model, params string[] fields)
{
var entity = _unitOfWork.RepositoryAsync<TEntity>().UpdateFieldsOnly(_mapper.Map<TEntity>(model), fields);
await _unitOfWork.SaveChangesAsync();
return _mapper.Map<TModel>(entity);
}
}
Upvotes: 1