如何将创建者设置为 DbContext 中的实体
How to set Created By to enitites in DbContext
关于 Stack Overflow 问题 How to set created date and Modified Date to enitites in DB first approach asked by user LP13 and answered by user Ogglas。
我正在编写一个测试项目来学习新的开发方法,但遇到了瓶颈。我正在尝试实现 Ogglas 提供的答案,但是我不确定如何在 AutoFac 中注册 "Wrapper"?
Ogglas 和我的代码示例
public interface IEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public interface IAuditableEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public interface ICurrentUser
{
string GetUsername();
}
public interface ICurrentUser
{
string Name();
string GetUserId();
bool IsUserAuthenticated();
bool IsUserAdmin();
bool IsUserManager();
}
public class ApplicationDbContextUserWrapper
{
public ApplicationDbContext Context;
public ApplicationDbContextUserWrapper(ApplicationDbContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
}
public class MyDbContextWrapper
{
public IMyDbContext Context;
public MyDbContextWrapper(IMyDbContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
Context = context;
}
}
public class ApplicationDbContext : DbContext
{
public ICurrentUser CurrentUser;
public override int SaveChanges()
{
var now = DateTime.Now;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedDate = now;
entity.UpdatedDate = now;
entity.CreatedBy = CurrentUser.GetUsername();
entity.UpdatedBy = CurrentUser.GetUsername();
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser.GetUsername();
break;
}
}
}
return base.SaveChanges();
}
}
public class MyDbContext : DbContext, IMyDbContext
{
public ICurrentUser CurrentUser { get; set; }
public DbSet<Staff> Staff { get; set; }
public DbSet<AddressStaff> StaffAddresses { get; set; }
public MyDbContext() : base("Name=MyWebPortalConnection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, MyWebPortalContextMigrationConfiguration>());
}
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries().Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added
|| x.State == EntityState.Modified));
foreach (var entry in modifiedEntries)
{
if (entry.Entity is IAuditableEntity entity)
{
var dateTimeZone = DateTimeZoneProviders.Tzdb["Europe/London"];
var zonedClock = SystemClock.Instance.InZone(dateTimeZone);
var localDateTime = zonedClock.GetCurrentLocalDateTime();
var dateTime = new DateTime(localDateTime.Year,
localDateTime.Month,
localDateTime.Day,
localDateTime.Hour,
localDateTime.Minute,
localDateTime.Second);
if (entry.State == EntityState.Added)
{
entity.CreatedBy = CurrentUser.Name();
entity.CreatedDate = dateTime;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedBy = CurrentUser.Name();
entity.UpdatedDate = dateTime;
}
else
{
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
}
}
return base.SaveChanges();
}
我的 AutoFac EF 模块已更新
public class EFModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//builder.RegisterType<MyDbContextWrapper>().As<IMtDbContext>();
//builder.RegisterDecorator<MyDbContextWrapper, IMyDbContext>();
//builder.RegisterDecorator<MyDbContextWrapper, IMyDbContext>();
builder.RegisterType<MyDbContextWrapper>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType(typeof(MyDbContext)).As(typeof(IMyDbContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType(typeof(UnitOfWork)).As(typeof(IUnitOfWork)).InstancePerRequest();
builder.Register(_ => new HttpClient()).As<HttpClient>().InstancePerLifetimeScope();
}
}
我使用以下教程作为设置项目的指南
Tutorial Guide Project
我将非常感谢所提供的任何帮助。谢谢。
通用存储库已更新
public abstract class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
protected MyDbContextWrapper DbContextWrapper;
protected DbContext GenericDbContext;
protected readonly IDbSet<T> GenericDbset;
protected GenericRepository(MyDbContextWrapper dbContextWrapper)
{
DbContextWrapper = dbContextWrapper;
GenericDbContext = (DbContext)DbContextWrapper.Context;
GenericDbset = GenericDbContext.Set<T>();
}
IMyDbContext 已更新
public interface IMyDbContext
{
ICurrentUser CurrentUser { get; set; }
DbSet<Staff> Staff { get; set; }
DbSet<AddressStaff> StaffAddresses { get; set; }
int SaveChanges();
}
我的当前用户 AutoFac 模块
public class CurrentUserModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("MyWebPortal.Model"))
.Where(t => t.Name.EndsWith("User"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
builder.RegisterType<ApplicationDbContextUserWrapper>().AsSelf().
看这里:https://autofaccn.readthedocs.io/en/latest/register/registration.html
这个解释的很好。基本上,您是在告诉 autofac "save" 某种类型,就像它本身一样。您还可以指定接口和参数。
完成此操作后。您需要在构造函数中添加类型,以确保它已被注入。确保还注册了所有依赖项,这样它们也可以被解析。
通常,将包装器用作装饰器也是一个好主意:
public class ApplicationDbContextUserWrapper : IDbContext
{
public ApplicationDbContext Context;
public ApplicationDbContextUserWrapper(ApplicationDbContext context,
ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
int IDbContext.SaveChanges() => context.SaveChanges();
...
这样,您就明确地与一个接口对话,该接口与 dbcontext 相同,但包装了您要调用的方法。所以你仍然可以在不触及基础的情况下改变行为 class.
这是一个最小的工作解决方案。
接口:
public interface IMyDbContext
{
DbSet<Staff> Staff { get; }
DbChangeTracker ChangeTracker { get; }
int SaveChanges();
}
public interface IAuditableEntity
{
string CreatedBy { get; set; }
}
public interface ICurrentUser
{
string Name();
}
实体:
public class Staff : IAuditableEntity
{
[Key]
public int Id { get; set; }
public string CreatedBy { get; set; }
}
模拟:
public class MockCurrentUser : ICurrentUser
{
public string Name() => "Mock";
}
public class MockDbContext : DbContext, IMyDbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MockDbContext>(null);
base.OnModelCreating(modelBuilder);
}
public DbSet<Staff> Staff { get; set; }
public override int SaveChanges() => 1;
}
装饰者:
public class ApplicationDbContextAuditDecorator : IMyDbContext
{
private readonly IMyDbContext context;
private readonly ICurrentUser currentUser;
public ApplicationDbContextAuditDecorator(IMyDbContext context, ICurrentUser currentUser)
{
this.context = context;
this.currentUser = currentUser;
}
public DbSet<Staff> Staff { get => this.context.Staff; }
public DbChangeTracker ChangeTracker => this.context.ChangeTracker;
public int SaveChanges()
{
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IAuditableEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedBy = this.currentUser.Name();
break;
}
}
}
return this.context.SaveChanges();
}
}
并测试:
[TestMethod]
public void TestMethod1()
{
var builder = new ContainerBuilder();
builder.RegisterType<MockDbContext>().As<IMyDbContext>().InstancePerLifetimeScope();
builder.RegisterDecorator<ApplicationDbContextAuditDecorator, IMyDbContext>();
builder.RegisterType<MockCurrentUser>().As<ICurrentUser>();
var container = builder.Build();
var context = container.Resolve<IMyDbContext>();
context.Staff.Add(new Staff());
context.SaveChanges();
Assert.AreEqual("Mock", context.Staff.Local.Single().CreatedBy);
}
关于 Stack Overflow 问题 How to set created date and Modified Date to enitites in DB first approach asked by user LP13 and answered by user Ogglas。
我正在编写一个测试项目来学习新的开发方法,但遇到了瓶颈。我正在尝试实现 Ogglas 提供的答案,但是我不确定如何在 AutoFac 中注册 "Wrapper"?
Ogglas 和我的代码示例
public interface IEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public interface IAuditableEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public interface ICurrentUser
{
string GetUsername();
}
public interface ICurrentUser
{
string Name();
string GetUserId();
bool IsUserAuthenticated();
bool IsUserAdmin();
bool IsUserManager();
}
public class ApplicationDbContextUserWrapper
{
public ApplicationDbContext Context;
public ApplicationDbContextUserWrapper(ApplicationDbContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
}
public class MyDbContextWrapper
{
public IMyDbContext Context;
public MyDbContextWrapper(IMyDbContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
Context = context;
}
}
public class ApplicationDbContext : DbContext
{
public ICurrentUser CurrentUser;
public override int SaveChanges()
{
var now = DateTime.Now;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedDate = now;
entity.UpdatedDate = now;
entity.CreatedBy = CurrentUser.GetUsername();
entity.UpdatedBy = CurrentUser.GetUsername();
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser.GetUsername();
break;
}
}
}
return base.SaveChanges();
}
}
public class MyDbContext : DbContext, IMyDbContext
{
public ICurrentUser CurrentUser { get; set; }
public DbSet<Staff> Staff { get; set; }
public DbSet<AddressStaff> StaffAddresses { get; set; }
public MyDbContext() : base("Name=MyWebPortalConnection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, MyWebPortalContextMigrationConfiguration>());
}
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries().Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added
|| x.State == EntityState.Modified));
foreach (var entry in modifiedEntries)
{
if (entry.Entity is IAuditableEntity entity)
{
var dateTimeZone = DateTimeZoneProviders.Tzdb["Europe/London"];
var zonedClock = SystemClock.Instance.InZone(dateTimeZone);
var localDateTime = zonedClock.GetCurrentLocalDateTime();
var dateTime = new DateTime(localDateTime.Year,
localDateTime.Month,
localDateTime.Day,
localDateTime.Hour,
localDateTime.Minute,
localDateTime.Second);
if (entry.State == EntityState.Added)
{
entity.CreatedBy = CurrentUser.Name();
entity.CreatedDate = dateTime;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedBy = CurrentUser.Name();
entity.UpdatedDate = dateTime;
}
else
{
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
}
}
return base.SaveChanges();
}
我的 AutoFac EF 模块已更新
public class EFModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//builder.RegisterType<MyDbContextWrapper>().As<IMtDbContext>();
//builder.RegisterDecorator<MyDbContextWrapper, IMyDbContext>();
//builder.RegisterDecorator<MyDbContextWrapper, IMyDbContext>();
builder.RegisterType<MyDbContextWrapper>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType(typeof(MyDbContext)).As(typeof(IMyDbContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType(typeof(UnitOfWork)).As(typeof(IUnitOfWork)).InstancePerRequest();
builder.Register(_ => new HttpClient()).As<HttpClient>().InstancePerLifetimeScope();
}
}
我使用以下教程作为设置项目的指南 Tutorial Guide Project 我将非常感谢所提供的任何帮助。谢谢。
通用存储库已更新
public abstract class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
protected MyDbContextWrapper DbContextWrapper;
protected DbContext GenericDbContext;
protected readonly IDbSet<T> GenericDbset;
protected GenericRepository(MyDbContextWrapper dbContextWrapper)
{
DbContextWrapper = dbContextWrapper;
GenericDbContext = (DbContext)DbContextWrapper.Context;
GenericDbset = GenericDbContext.Set<T>();
}
IMyDbContext 已更新
public interface IMyDbContext
{
ICurrentUser CurrentUser { get; set; }
DbSet<Staff> Staff { get; set; }
DbSet<AddressStaff> StaffAddresses { get; set; }
int SaveChanges();
}
我的当前用户 AutoFac 模块
public class CurrentUserModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("MyWebPortal.Model"))
.Where(t => t.Name.EndsWith("User"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
builder.RegisterType<ApplicationDbContextUserWrapper>().AsSelf().
看这里:https://autofaccn.readthedocs.io/en/latest/register/registration.html
这个解释的很好。基本上,您是在告诉 autofac "save" 某种类型,就像它本身一样。您还可以指定接口和参数。 完成此操作后。您需要在构造函数中添加类型,以确保它已被注入。确保还注册了所有依赖项,这样它们也可以被解析。
通常,将包装器用作装饰器也是一个好主意:
public class ApplicationDbContextUserWrapper : IDbContext
{
public ApplicationDbContext Context;
public ApplicationDbContextUserWrapper(ApplicationDbContext context,
ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
int IDbContext.SaveChanges() => context.SaveChanges();
...
这样,您就明确地与一个接口对话,该接口与 dbcontext 相同,但包装了您要调用的方法。所以你仍然可以在不触及基础的情况下改变行为 class.
这是一个最小的工作解决方案。
接口:
public interface IMyDbContext
{
DbSet<Staff> Staff { get; }
DbChangeTracker ChangeTracker { get; }
int SaveChanges();
}
public interface IAuditableEntity
{
string CreatedBy { get; set; }
}
public interface ICurrentUser
{
string Name();
}
实体:
public class Staff : IAuditableEntity
{
[Key]
public int Id { get; set; }
public string CreatedBy { get; set; }
}
模拟:
public class MockCurrentUser : ICurrentUser
{
public string Name() => "Mock";
}
public class MockDbContext : DbContext, IMyDbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MockDbContext>(null);
base.OnModelCreating(modelBuilder);
}
public DbSet<Staff> Staff { get; set; }
public override int SaveChanges() => 1;
}
装饰者:
public class ApplicationDbContextAuditDecorator : IMyDbContext
{
private readonly IMyDbContext context;
private readonly ICurrentUser currentUser;
public ApplicationDbContextAuditDecorator(IMyDbContext context, ICurrentUser currentUser)
{
this.context = context;
this.currentUser = currentUser;
}
public DbSet<Staff> Staff { get => this.context.Staff; }
public DbChangeTracker ChangeTracker => this.context.ChangeTracker;
public int SaveChanges()
{
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IAuditableEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedBy = this.currentUser.Name();
break;
}
}
}
return this.context.SaveChanges();
}
}
并测试:
[TestMethod]
public void TestMethod1()
{
var builder = new ContainerBuilder();
builder.RegisterType<MockDbContext>().As<IMyDbContext>().InstancePerLifetimeScope();
builder.RegisterDecorator<ApplicationDbContextAuditDecorator, IMyDbContext>();
builder.RegisterType<MockCurrentUser>().As<ICurrentUser>();
var container = builder.Build();
var context = container.Resolve<IMyDbContext>();
context.Staff.Add(new Staff());
context.SaveChanges();
Assert.AreEqual("Mock", context.Staff.Local.Single().CreatedBy);
}