第二次运行迁移时出错。看来需要处理通用回购,我不知道如何

Posted

技术标签:

【中文标题】第二次运行迁移时出错。看来需要处理通用回购,我不知道如何【英文标题】:Error second time I run a migration. It appears the Generic Repo needs to be disposed, I have no idea how 【发布时间】:2020-08-18 12:44:07 【问题描述】:

请多多包涵,我在这里真的很挣扎。

我对依赖注入的 EF Core 比较陌生。我来自新类等的旧 asp.net 世界,但想尝试使用依赖注入。由于我们的旧数据库也需要迁移到新结构,我认为一个好的第一个应用程序是编写一个使用依赖注入的应用程序。我决定使用 Blazor 作为我们的最终代码库。

有两个数据库,一个叫做 QOnT,一个叫做 iSele。我正在将数据从 QOnT 迁移到 iSele - 使用包括 https://ngohungphuc.wordpress.com/2018/05/01/generic-repository-pattern-in-asp-net-core/ 和 https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application 在内的多个网站 - 我创建了两个通用存储库。一个用于 QOnT 表,一个将作为最终的应用程序存储库。我还为两者创建了一个通用的工作单元。

泛型与 Tony 的技术和语言博客或 https://ngohungphuc.wordpress.com/ 上的大致相同,除了一项更改是为了满足我需要插入和更新身份字段中的项目这一事实。所以我创建了一个通用例程,它禁用身份检查,然后插入并重新打开它。我已经贴在下面了:

public T AddWithIDOn(T entity, string TableName)
    
        _loggerManager.LogDebug($"FancyRepo -> AddWithIDOn typeof(T), enttity entity.ToString(), Table: TableName ");
        _context.Database.OpenConnection();
        try
        
            _context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT TableName ON");
            _context.Set<T>().Add(entity);
            _context.SaveChanges();
            _context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT TableName OFF");
        
        catch (Exception ex)
        
            _loggerManager.LogInfo($"!!!Error!!! FancyRepo -> AddWithIDOn error: ex.Message");
        
        finally
        
            _context.Database.CloseConnection();
        
        return entity;
    

这很好用。

在 Startup.cs 我添加 DbContexts 和工作单元

public void ConfigureServices(IServiceCollection services)
    

        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddDbContextPool<ApplicationDbContext>(options =>
        
            options.UseSqlServer(Configuration.GetConnectionString("iSeleConnection"));
            options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
            options.EnableSensitiveDataLogging(true);
        );
        services.AddDbContext<QOnTDbContext>(qontoptions =>
        
            qontoptions.UseSqlServer(Configuration.GetConnectionString("QOnTConnection"));
        );

        //services.AddScoped(typeof(IQOnTGenericRepository<>), typeof(QOnTGenericRepository<>));
        services.AddScoped(typeof(IQOnTUnitOfWork), typeof(QOnTUnitOfWork));

        // services.AddScoped(typeof(IGenericRepository<>), typeof(IGenericRepository<>));
        services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));

        // logger
        services.AddSingleton<ILoggerManager, LoggerManager>();
    

你会看到我已经注释掉了 services.addscoped 用于转发是 Unitofwork 创建它们。

然后我有一个“代码隐藏”或在我的剃须刀后面为 Migrate 客户称为 CompnentBase 的任何东西

所以

  public partial class MigrateCustomersBase : ComponentBase

我在那里注入了两个 UnitOfWorks

[Inject]
private IQOnTUnitOfWork _QOnTUnitOfWork  get; set; 
[Inject]
private IUnitOfWork _iSeleUnitOfWork  get; set; 

我在表单上有一个按钮,点击后可以进行迁移:


            IQOnTGenericRepository<CustomerTypeTbl> QOnTCustomerTypeTbl = _QOnTUnitOfWork.Repository<CustomerTypeTbl>();

            ICollection<CustomerTypeTbl> QOnTCustomerTypes = QOnTCustomerTypeTbl.GetAll();

            foreach (var srcCusType in QOnTCustomerTypes)
            

                CustomerType tgtCusType = _iSeleUnitOfWork.Repository<CustomerType>().Find(ct => ct.CustomerTypeID == srcCusType.CustTypeId);

                if (tgtCusType == null)
                
                    Logger.LogDebug($"  *Inserting CustomerType with id: srcCusType.CustTypeId and name srcCusType.CustTypeDesc");
                    _iSeleUnitOfWork.Repository<CustomerType>().AddWithIDOn(new CustomerType
                        
                            CustomerTypeID = srcCusType.CustTypeId,
                            CustomerTypeName = srcCusType.CustTypeDesc,
                            HasExtendedOptions = false,
                            Notes= string.Format("Migrated from QOnT.CustomerTypeTbl 0:d", DateTime.UtcNow.Date)                        
                        
                        , "iSele.CustomerTypes");
                
                else
                
                    tgtCusType.CustomerTypeName = srcCusType.CustTypeDesc;
                    tgtCusType.HasExtendedOptions = false;
                    tgtCusType.Notes = string.Format("Migrated from QOnT.CustomerTypeTbl 0:d", DateTime.UtcNow.Date);
                    Logger.LogDebug($"  *Updating Area with id: tgtCusType.CustomerTypeID and name tgtCusType.CustomerTypeName");
                    _iSeleUnitOfWork.Repository<CustomerType>().Update(tgtCusType);
                

            
        

所以毕竟这是我的问题。好吧,我第一次运行代码时一切正常。但是,如果我再次单击该按钮(调用代码),则会出现错误

无法跟踪实体类型“CustomerType”的实例,因为已在跟踪另一个具有键值“CustomerTypeID: 1”的实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

在我看来,这似乎存在多个 UnitOfWorks。如果我离开页面并返回它会再次工作。这只是第二次单击该按钮它不起作用。我做错了什么,我该如何处理 UnitOfWork-我需要刷新页面还是什么?

【问题讨论】:

通过调用 .AsNoTracking() 将工作生命周期单位更改为全局 EF 设置中的瞬态或禁用跟踪或每个请求 我在上面的代码中都试过了,你会看到''options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); services.AddTransient(typeof(IUnitOfWork), typeof(UnitOfWork)); - 同样的错误 那是在startup.cs中 实际上看起来循环中的项目具有相同的主键 ID,因此它不知道如何区分它们。也许尝试添加备用键? 我不敢相信这是问题所在。无论我迁移哪个表,我都有这个问题。如果我第一次运行更新和插入迁移时它可以完美运行,为什么我需要一个替代键?如果我出去回来一切正常。这怎么会是一个关键问题? 【参考方案1】:

所以我停止使用依赖注入我修改了点击时调用的函数如下:

       var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        optionsBuilder.UseSqlServer(ConString);
        optionsBuilder.EnableSensitiveDataLogging(true);
        optionsBuilder.EnableDetailedErrors(true);

        ApplicationDbContext _iSeleDbContext = new ApplicationDbContext(optionsBuilder.Options);
        UnitOfWork _iSeleUnitOfWork = new UnitOfWork(_iSeleDbContext, Logger);

        IQOnTFancyGenericRepository<CustomerTypeTbl> QOnTCustomerTypeTbl = _QOnTUnitOfWork.Repository<CustomerTypeTbl>();

        ICollection<CustomerTypeTbl> QOnTCustomerTypes = QOnTCustomerTypeTbl.GetAll();

        foreach (var srcCusType in QOnTCustomerTypes)
        
            CustomerType tgtCusType = _iSeleUnitOfWork.Repository<CustomerType>().Find(ct => ct.CustomerTypeID == srcCusType.CustTypeId);

            if (tgtCusType == null)
            
                //await 
                Logger.LogDebug($"  *Inserting CustomerType with id: srcCusType.CustTypeId and name srcCusType.CustTypeDesc");
                _iSeleUnitOfWork.Repository<CustomerType>().AddWithIDOn(new CustomerType
                
                    CustomerTypeID = srcCusType.CustTypeId,
                    CustomerTypeName = srcCusType.CustTypeDesc,
                    HasExtendedOptions = false,
                    Notes = string.Format("Migrated from QOnT.CustomerTypeTbl 0:d", DateTime.UtcNow.Date)
                
                    , "iSele.CustomerTypes");
            
            else
            
                tgtCusType.CustomerTypeName = srcCusType.CustTypeDesc;
                tgtCusType.HasExtendedOptions = false;
                tgtCusType.Notes = string.Format("Migrated from QOnT.CustomerTypeTbl 0:d", DateTime.UtcNow.Date);
                Logger.LogDebug($"  *Updating Area with id: tgtCusType.CustomerTypeID and name tgtCusType.CustomerTypeName");
                _iSeleUnitOfWork.Repository<CustomerType>().Update(tgtCusType);
            
        

        _iSeleUnitOfWork.Dispose();

所以我现在迷路了。依赖注入似乎很糟糕。但是为什么网上的人都推荐它。这不是一个实用的解决方案。

【讨论】:

运行多个测试后,这似乎与 EF 核心不喜欢关闭身份的事实有关。我还没有找到强制重置 EF DbSet 模型以解决此问题的方法。

以上是关于第二次运行迁移时出错。看来需要处理通用回购,我不知道如何的主要内容,如果未能解决你的问题,请参考以下文章

第二次使用 ng2-dragula 进入页面时出错

第二次打开 SupportMapFragment 时出错

FC第二次博客作业

Django - 迁移命令说该表在执行第二次 makemigrations 后存在

第二次在对话框中膨胀片段时出错

Spotify API:第一次尝试将曲目添加到播放列表时出错,第二次尝试有效