Entity Framework 6.2.0 - 自动保存哪个用户创建或更新了 DbSet 值的 DbContext

Posted

技术标签:

【中文标题】Entity Framework 6.2.0 - 自动保存哪个用户创建或更新了 DbSet 值的 DbContext【英文标题】:Entity Framework 6.2.0 - DbContext that automatically saves which user created or updated a DbSet value 【发布时间】:2018-12-28 15:13:25 【问题描述】:

正如标题所说,我们需要存储哪个用户更新或创建了特定值。以前我们只需要保存CreatedDateUpdatedDate。这很容易,我们所有的实体都继承自一个抽象基类 称为EntityBase,它实现了具有这些值的接口IEntity。然后DbContext SaveChanges 被覆盖,如果特定实体实现IEntity,则根据EntityState.AddedEntityState.Modified 设置值。

interface IEntity

    DateTime CreatedDate  get; set; 

    string CreatedBy  get; set; 

    DateTime UpdatedDate  get; set; 

    string UpdatedBy  get; set; 



public abstract class EntityBase<T1>: IEntity

    public T1 Id  get; set; 

    public virtual DateTime CreatedDate  get; set; 
    public virtual string CreatedBy  get; set; 
    public virtual DateTime UpdatedDate  get; set; 
    public virtual string UpdatedBy  get; set; 

然而,一个新的要求是我们还需要保存哪个用户进行了更改。一个简单的解决方案是通过构造函数添加用户并且没有空的构造函数。然而,当使用迁移等时,这被证明是一个问题:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<CatalogServiceContext, Configuration>());
var migrator = new DbMigrator(new Configuration());

目标上下文'CatalogService.Data.Context.CatalogServiceContext' 是不可构造的。添加默认构造函数或提供 IDbContextFactory 的实现。

public class CatalogServiceContext : DbContext


    private readonly string _currentUser;

    public CatalogServiceContext(string currentUser) : base("name=CatalogContext")
    
        _currentUser = 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.CreatedBy = _currentUser;
                        entity.UpdatedDate = now;
                        entity.UpdatedBy = _currentUser;
                        break;

                    case EntityState.Modified:
                        Entry(entity).Property(x => x.CreatedDate).IsModified = false;
                        Entry(entity).Property(x => x.CreatedBy).IsModified = false;
                        entity.UpdatedDate = now;
                        entity.UpdatedBy = _currentUser;
                        break;
                
            
        

        return base.SaveChanges();
    

我尝试实现IDbContextFactory,但它也有类似的问题。

上下文工厂类型 'CatalogService.Data.Context.CatalogServiceContextFactory' 没有 有一个公共的无参数构造函数。要么添加公众 无参数构造函数,创建一个 IDbContextFactory 实现 在上下文程序集中,或使用注册上下文工厂 数据库配置。

public class CatalogServiceContextFactory : IDbContextFactory<CatalogServiceContext>

    private readonly string _currentUser;

    public CatalogServiceContextFactory(string currentUser)
    
        _currentUser = currentUser;
    

    public CatalogServiceContext Create()
    
        return new CatalogServiceContext(_currentUser);
    

解决方法是EntityBase 中的两个方法,每次更新或创建值时都需要调用它们。这可行,但我认为最好的解决方案仍然是构造函数中的强制用户。有没有其他人有类似的需求已经解决了?我想在DbContext 中解决它,而不是为每个DbSet 使用存储库抽象。

public virtual EntityBase<T1> SetCreateFields(string currentUser)

    CreatedBy = currentUser;
    UpdatedBy = currentUser;
    return this;


public virtual EntityBase<T1> SetUpdateFields(string currentUser)

    UpdatedBy = currentUser;
    return this;

【问题讨论】:

一个简单的解决方案是通过构造函数添加用户,并且没有一个空的构造函数 为什么要坚持构造函数注入?使用属性或方法注入它有什么缺点?谁调用具有这个额外参数的 dbcontext 构造函数,也可以调用无参数构造函数并以任何其他方式注入用户数据。 @WiktorZychla 在我看来,好处是每当创建 Context (new) 时都会设置该属性。很容易忘记调用特定方法或设置属性imao。如果我可以有一个关于我期望的值的编译器警告,我喜欢它。 您总是可以将Obsolete 放在无参数构造函数上。 @WiktorZychla 绝对是,但感觉有点“hacky” 是的。它只是为了遵循你的方向。我以完全不同的方式处理这个问题。没有将用户名插入上下文,而是有一个单独的 username factory 和可注入提供程序。被覆盖的SaveChanges 直接依赖于工厂,但实现细节各不相同(因为它们依赖于可注入的提供程序)。这样仍然有一个无参数的构造函数,SaveChanges 只要求输入用户名。不过,我并没有提出这种替代方法作为答案,因为您似乎非常专注于您的特定方法。 【参考方案1】:

决定这样解决:

public class CatalogServiceContext : DbContext

    public ICurrentUser CurrentUser;

    [Obsolete("Use CatalogServiceContextUserWrapper instead")]
    public CatalogServiceContext() : base("name=CatalogContext")
    

    

    ...


public class CatalogServiceContextUserWrapper

    public CatalogServiceContext Context;

    public CatalogServiceContextUserWrapper(CatalogServiceContext context, ICurrentUser currentUser)
    
        context.CurrentUser = currentUser;
        this.Context = context;
    

【讨论】:

以上是关于Entity Framework 6.2.0 - 自动保存哪个用户创建或更新了 DbSet 值的 DbContext的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 学习系列 - 认识理解Entity Framework

Entity Framework Code-First(23):Entity Framework Power Tools

Entity Framework怎么GroupBY多个字段

entity framework 公共类

Entity Framework Fluent API

初步了解Entity Framework