В настоящее время я использую .net core 2.0 вместе с Audit.EntityFramework для аудита изменений в моих сущностях.
Я прошел путь создания собственного CustomProvider, как описано здесь DataProviders краткая выдержка ниже.
EmployerCustomDataProvider
public class EmployerCustomDataProvider : AuditDataProvider, IEmployerCustomDataProvider {
private readonly IUnitOfWork _unitOfWork;
public EmployerCustomDataProvider (IUnitOfWork unitOfWork, IMapper mapper) {
_unitOfWork = unitOfWork;
}
// Async Implementation
public override async Task<object> InsertEventAsync (AuditEvent auditEvent) {
// Unique Id For Audit Event
var Id = Guid.NewGuid ();
EntityFrameworkEvent auditEntityFrameworkEvent = auditEvent.GetEntityFrameworkEvent ();
// Need to audit each entry
List<EventEntry> eventEntries = auditEntityFrameworkEvent.Entries;
foreach (EventEntry entry in eventEntries) {
EmployerAuditHistory employerAuditHistory = Mapper.Map<EmployerAuditHistory> (entry);
this._unitOfWork.EmployerAuditHistoryRepository.Insert (employerAuditHistory);
await this._unitOfWork.CommitAsync ();
foreach (EventEntryChange change in entry.Changes) {
EmployerAuditHistoryDetail employerAuditHistoryDetail = new EmployerAuditHistoryDetail ();
employerAuditHistoryDetail.EmployerAuditHistoryId = employerAuditHistory.EmployerAuditHistoryId;
employerAuditHistoryDetail.ColumnName = change.ColumnName;
employerAuditHistoryDetail.OriginalValue = change.OriginalValue.ToString ();
employerAuditHistoryDetail.NewValue = change.NewValue.ToString ();
this._unitOfWork.EmployerAuditHistoryDetailRepository.Insert (employerAuditHistoryDetail);
}
}
return await Task.Run (() => Id);
}
...
В соответствии с документацией этот пользовательский поставщик вызывается при следующих условиях.
Эта библиотека перехватывает вызовы SaveChanges () / SaveChangesAsync () на вашем DbContext, чтобы генерировать события Audit.NET с информацией о затронутых объектах.
Каждый вызов SaveChanges генерирует событие аудита, которое включает в себяинформация обо всех сущностях, затронутых операцией сохранения.
В моем бизнес-уровне у меня есть такой метод, который создает или обновляет ветку работодателя.
EmployerBranchService: BusinessСлой
public class EmployerBranchService : BaseService, IEmployerBranchService {
private readonly IUnitOfWork _unitOfWork;
public EmployerBranchService (IMapper mapper, IUnitOfWork unitOfWork) : base (mapper) {
this._unitOfWork = unitOfWork;
}
private async Task<EmployerBranchModel> CreateUpdateEmployerBranch (EmployerBranchModel employerBranchModel) {
EmployerBranch employerBranch = Mapper.Map (employerBranchModel, await this._unitOfWork.EmployerBranchRepository.GetEmployerBranchById (employerBranchModel.EmployerBranchId.Value));
if ((employerBranch?.EmployerBranchId ?? 0) == 0)
this._unitOfWork.EmployerBranchRepository.Insert (employerBranch);
else
this._unitOfWork.EmployerBranchRepository.Update (employerBranch);
await this._unitOfWork.CommitAsync ();
return Mapper.Map<EmployerBranchModel> (employerBranch);
}
}
Когда вызывается моя единица работ, ветка сохраняется, как и ожидалось, и я получаюp в методе EmployerCustomDataProvider классы InsertEventAsync .Однако, когда я вызываю единицу работы здесь, я получаю следующую ошибку:
Невозможно получить доступ к удаленному объекту.Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения.Это может произойти, если вы вызываете Dispose () для контекста или заключаете контекст в оператор using.Если вы используете внедрение зависимости, вы должны позволить контейнеру введения зависимости позаботиться об удалении экземпляров контекста.Имя объекта: 'EmployerContext'.
Мне кажется, что проблема в том, что когда единица работы бизнес-уровней представляет свои изменения, контекст удаляется.Я не уверен, как обойти это, поскольку я попытался зарегистрировать свои зависимости по-разному и т. Д.
Я пытался зарегистрировать настройки аудита в методе моей программы.
Program
public class Program
{
public static void Main(string[] args)
{
IWebHost host = BuildWebHost(args);
SeedDatabase(host);
SetupAudit(host);
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
//Seed database methods in .net core need to be done in the Program method:
//https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro
public static void SeedDatabase(IWebHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
EmployerContext context = services.GetRequiredService<EmployerContext>();
DbInitializer.Seed(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
}
public static void SetupAudit(IWebHost host)
{
var _container = DependencyInjectionConfig.Container;
using (AsyncScopedLifestyle.BeginScope(_container))
{
AuditConfiguration.SetupAuditConfiguration(_container);
}
}
}
DependencyInjectionConfig
public class DependencyInjectionConfig
{
private static Container _container;
public static Container Container
{
get
{
if (_container == null)
{
_container = new Container();
_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
}
return _container;
}
}
public static void InitializeContainer(IApplicationBuilder app)
{
Container.RegisterMvcControllers(app);
Container.RegisterMvcViewComponents(app);
Container.CrossWire<EmployerContext>(app);
Container.Register<ITypeService, TypeService>(Lifestyle.Scoped);
Container.Register<IEmployerService, EmployerService>(Lifestyle.Scoped);
Container.Register<IEmployerPersonContactService, EmployerPersonContactService>(Lifestyle.Scoped);
Container.Register<IEmployerBranchService, EmployerBranchService>(Lifestyle.Scoped);
Container.Register<IEmployerSearchService, EmployerSearchService>(Lifestyle.Scoped);
Container.Register<INoteService, NoteService>(Lifestyle.Scoped);
Container.Register<IBaseModelUsernameResolver, BaseModelUsernameResolver>(Lifestyle.Scoped);
Container.Register<IUnitOfWork, UnitOfWork>(Lifestyle.Scoped);
Container.Register<IEmployerNoteTypeRepository, EmployerNoteTypeRepository>(Lifestyle.Scoped);
Container.Register<IEmployerDocumentTypeRepository, EmployerDocumentTypeRepository>(Lifestyle.Scoped);
Container.Register<IPrivatePayrollRepository, PrivatePayrollRepository>(Lifestyle.Scoped);
Container.Register<IEmployerRepository, EmployerRepository>(Lifestyle.Scoped);
Container.Register<IEmployerContactDetailsRepository, EmployerContactDetailsRepository>(Lifestyle.Scoped);
Container.Register<IEmployerAddressRepository, EmployerAddressRepository>(Lifestyle.Scoped);
Container.Register<IUserRepository, UserRepository>(Lifestyle.Scoped);
Container.Register<ISearchRepository, SearchRepository>(Lifestyle.Scoped);
Container.Register<IEmployerBranchRepository, EmployerBranchRepository>(Lifestyle.Scoped);
Container.Register<IEmployerBranchContactDetailsRepository, EmployerBranchContactDetailsRepository>(Lifestyle.Scoped);
Container.Register<IEmployerBranchAddressRepository, EmployerBranchAddressRepository>(Lifestyle.Scoped);
Container.Register<IEmployerPersonContactRepository, EmployerPersonContactRepository>(Lifestyle.Scoped);
Container.Register<IEmployerBranchSummaryRepository, EmployerBranchSummaryRepository>(Lifestyle.Scoped);
Container.Register<IEmployerAuditHistoryRepository, EmployerAuditHistoryRepository>(Lifestyle.Scoped);
Container.Register<INoteRepository, NoteRepository>(Lifestyle.Scoped);
Container.Register<IEmployerAuditHistoryDetailRepository, EmployerAuditHistoryDetailRepository>(Lifestyle.Scoped);
Container.Register<IEmployerCustomDataProvider, EmployerCustomDataProvider>(Lifestyle.Scoped);
Container.AutoCrossWireAspNetComponents(app);
Container.Verify();
}
public static void Register<TService, TImplementation>() where TService : class where TImplementation : class, TService
{
Container.Register<TService, TImplementation>();
}
}
AuditConfiguration
public static class AuditConfiguration
{
public static void SetupAuditConfiguration(SimpleInjector.Container container)
{
Configuration.Setup()
.UseCustomProvider((AuditDataProvider)container.GetInstance<IEmployerCustomDataProvider>())
.WithCreationPolicy(EventCreationPolicy.InsertOnEnd);
Audit.EntityFramework.Configuration.Setup()
.ForContext<EmployerContext>(config => config
.IncludeEntityObjects()
.AuditEventType("{context}:{database}"))
.UseOptOut();
}
}
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly EmployerContext _context;
private readonly IEmployerRepository _employerRepository;
private readonly IEmployerContactDetailsRepository _employerContactDetailsRepository;
private readonly IEmployerAddressRepository _employerAddressRepository;
private readonly IEmployerPersonContactRepository _employerPersonContactRepository;
private readonly IEmployerBranchRepository _employerBranchRepository;
private readonly IEmployerBranchContactDetailsRepository _employerBranchContactDetailsRepository;
private readonly IEmployerBranchAddressRepository _employerBranchAddressRepository;
private readonly IEmployerDocumentTypeRepository _employerDocumentTypeRepository;
private readonly IEmployerNoteTypeRepository _employerNoteTypeRepository;
private readonly IPrivatePayrollRepository _privatePayrollRepository;
private readonly IEmployerBranchSummaryRepository _employerBranchSummaryRepository;
private readonly INoteRepository _employerBranchNoteRepository;
private readonly ISearchRepository _searchRepository;
private readonly IEmployerAuditHistoryRepository _employerAuditHistoryRepository;
private readonly IEmployerAuditHistoryDetailRepository _employerAuditHistoryDetailRepository;
public IEmployerRepository EmployerRepository => this._employerRepository;
public IEmployerContactDetailsRepository EmployerContactDetailsRepository => this._employerContactDetailsRepository;
public IEmployerAddressRepository EmployerAddressRepository => this._employerAddressRepository;
public IEmployerPersonContactRepository EmployerPersonContactRepository => this._employerPersonContactRepository;
public IEmployerBranchRepository EmployerBranchRepository => this._employerBranchRepository;
public IEmployerBranchContactDetailsRepository EmployerBranchContactDetailsRepository => this._employerBranchContactDetailsRepository;
public IEmployerBranchAddressRepository EmployerBranchAddressRepository => this._employerBranchAddressRepository;
public IEmployerDocumentTypeRepository EmployerDocumentTypeRepository { get => this._employerDocumentTypeRepository; }
public IEmployerNoteTypeRepository EmployerNoteTypeRepository { get => this._employerNoteTypeRepository; }
public IPrivatePayrollRepository PrivatePayrollRepository { get => this._privatePayrollRepository; }
public IEmployerBranchSummaryRepository EmployerBranchSummaryRepository { get => this._employerBranchSummaryRepository; }
public INoteRepository EmployerBranchNoteRepository { get => this._employerBranchNoteRepository; }
public ISearchRepository SearchRepository { get => this._searchRepository; }
public IEmployerAuditHistoryRepository EmployerAuditHistoryRepository { get => this._employerAuditHistoryRepository; }
public IEmployerAuditHistoryDetailRepository EmployerAuditHistoryDetailRepository { get => this._employerAuditHistoryDetailRepository; }
public UnitOfWork(EmployerContext context,
IEmployerRepository employerRepository,
IEmployerContactDetailsRepository employerContactDetailsRepository,
IEmployerAddressRepository employerAddressRepository,
IEmployerPersonContactRepository employerBranchPersonRepository,
IEmployerBranchRepository employerBranchRepository,
IEmployerBranchContactDetailsRepository employerBranchContactDetailsRepository,
IEmployerBranchAddressRepository employerBranchAddressRepository,
IEmployerDocumentTypeRepository employerDocumentTypeRepository,
IEmployerNoteTypeRepository employerNoteTypeRepository,
IPrivatePayrollRepository privatePayrollRepository,
IEmployerBranchSummaryRepository employerBranchSummaryRepository,
INoteRepository EmployerBranchNoteRepository,
ISearchRepository SearchRepository,
IEmployerAuditHistoryRepository EmployerAuditHistoryRepository,
IEmployerAuditHistoryDetailRepository EmployerAuditHistoryDetailRepository)
{
this._employerRepository = employerRepository;
this._employerContactDetailsRepository = employerContactDetailsRepository;
this._employerAddressRepository = employerAddressRepository;
this._employerPersonContactRepository = employerBranchPersonRepository;
this._employerBranchRepository = employerBranchRepository;
this._employerBranchContactDetailsRepository = employerBranchContactDetailsRepository;
this._employerBranchAddressRepository = employerBranchAddressRepository;
this._employerDocumentTypeRepository = employerDocumentTypeRepository;
this._employerNoteTypeRepository = employerNoteTypeRepository;
this._privatePayrollRepository = privatePayrollRepository;
this._employerBranchSummaryRepository = employerBranchSummaryRepository;
this._employerBranchNoteRepository = EmployerBranchNoteRepository;
this._searchRepository = SearchRepository;
this._employerAuditHistoryRepository = EmployerAuditHistoryRepository;
this._employerAuditHistoryDetailRepository = EmployerAuditHistoryDetailRepository;
this._context = context;
}
public void Commit()
{
try
{
this._context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public async Task CommitAsync()
{
try
{
await this._context.SaveChangesAsync();
}
catch (Exception e)
{
throw;
}
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this._context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
EmployerBranchRepository
public class EmployerBranchRepository : RepositoryBase<EmployerBranch>, IEmployerBranchRepository
{
public EmployerBranchRepository(EmployerContext context) : base(context)
{
}
public async Task<EmployerBranch> GetEmployerBranchById(int employerBranchId)
{
return await base.FindAsync(e => e.IsActive && e.EmployerBranchId == employerBranchId);
}
public async Task<IList<EmployerBranch>> GetEmployerBranchesByEmployerId(int employerId)
{
return await base.FindAll(e => e.IsActive && e.EmployerId == employerId).ToListAsync();
}
}
RepositoryBase
public abstract class RepositoryBase<TEntity> : RepositoryReadOnlyBase<TEntity>, IRepository<TEntity> where TEntity : class
{
/// <summary>
/// Initializes a new instance of the <see cref="RepositoryBase{TEntity}"/> class.
/// </summary>
/// <param name="unitOfWork">The unit of work.</param>
/// <exception cref="NullReferenceException">Unit Of Work cannot be null</exception>
public RepositoryBase(EmployerContext context) : base(context)
{
}
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
public void Insert(TEntity entity)
{
this._dbSet.Add(entity);
}
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
public void Update(TEntity entity)
{
this._dbSet.Attach(entity);
this._dbContext.Entry(entity).State = EntityState.Modified;
PropertyEntry InsertedUserId = this._dbContext.Entry(entity).Property("InsertedUserId");
if (InsertedUserId != null)
InsertedUserId.IsModified = false;
PropertyEntry InsertedDate = this._dbContext.Entry(entity).Property("InsertedDate");
if (InsertedDate != null)
InsertedDate.IsModified = false;
}
}
RepositoryReadOnlyBase
public class RepositoryReadOnlyBase<TEntity> : IRepositoryReadOnly<TEntity> where TEntity : class
{
public readonly DbSet<TEntity> _dbSet;
public readonly EmployerContext _dbContext;
/// <summary>
/// Initializes a new instance of the <see cref="RepositoryBase{TEntity}"/> class.
/// </summary>
/// <param name="unitOfWork">The unit of work.</param>
/// <exception cref="NullReferenceException">Unit Of Work cannot be null</exception>
public RepositoryReadOnlyBase(EmployerContext context)
{
this._dbContext = context;
this._dbSet = this._dbContext.Set<TEntity>();
}
/// <summary>
/// Finds all asynchronous.
/// </summary>
/// <param name="predicate">The predicate.</param>
/// <param name="orderBy">The order by.</param>
/// <returns></returns>
public IQueryable<TEntity> FindAll(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = this._dbContext.Set<TEntity>();
if (includes != null)
{
query = query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
if (predicate != null)
query = query.Where(predicate);
if (orderBy != null)
query = orderBy(query);
return query;
}
/// <summary>
/// Finds the asynchronous.
/// </summary>
/// <param name="predicate">The predicate.</param>
/// <param name="orderBy">The order by.</param>
/// <returns></returns>
public async Task<TEntity> FindAsync(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, params Expression<Func<TEntity, object>>[] includes)
{
return await this.FindAll(predicate, orderBy, includes).FirstOrDefaultAsync();
}
public Task<TEntity> FindByIdAsync(int id)
{
return this._dbSet.FindAsync(id);
}
}
Я не уверен, сколько деталей включить, но может включать в себя выдержки из единицы работы и т. Д. Любое направление / совет приветствуется
РЕДАКТИРОВАТЬ Отслеживание стека ниже
в Microsoft.EntityFrameworkCore.DbContext.CheckDisposed () в Microsoft.EntityFrameworkCore.DbContext.Add [TEntity] (сущность TEntity) в Microsoft.EntityFrameworkCore.Internal.InternalDbSet * 10nt 1.лицо) в C: \ Информационные системы \ Микро-сервисы \ Employer.API \ head \ Employer.API.Data \ Repository \ RepositoryBase.cs: строка 29 в Employer.API.Business.DataProvider.EmployerCustomDataProvider.d__5.MoveNext () в C: \ Информационные системы \ Микросервисы \ Employer.API \ head \ Employer.API.Business \ DataProvider \ EmployerCustomDataProvider.cs: строка 77 --- Конец трассировки стека из предыдущего местоположения, в котором было сгенерировано исключение --- в System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndtificationЗадача) в System.Runtime.CompilerServices.TaskAwaiter 1.GetResult()
at Audit.Core.AuditScope.<SaveEventAsync>d__40.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Audit.Core.AuditScope.<SaveAsync>d__34.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Audit.EntityFramework.DbContextHelper.<SaveScopeAsync>d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Audit.EntityFramework.DbContextHelper.<SaveChangesAsync>d__34.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult () в Audit.EntityFramework.AuditDbContext.d__36.MoveNext () --- Конец трассировки стека из предыдущего расположения, где было сгенерировано исключение --- в System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Задача) в System.Runtime.CompilerServices.TaskAwaiter 1.GetResult()
at Employer.API.Data.UnitOfWork.UnitOfWork.<CommitAsync>d__48.MoveNext() in C:\Information Systems\Micro Services\Employer.API\head\Employer.API.Data\UnitOfWork\UnitOfWork.cs:line 103
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Employer.API.Business.Services.EmployerBranchService.<CreateUpdateEmployerBranch>d__6.MoveNext() in C:\Information Systems\Micro Services\Employer.API\head\Employer.API.Business\Services\EmployerBranchService.cs:line 135
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter
1.GetRultв Employer.API.Business.Services.EmployerBranchService.d__5.MoveNext () в C: \ Information Systems \ Micro Services \ Employer.API \ head \ Employer.API.Business \ Services \ EmployerBranchService.cs: строка 89