无法跟踪实体类型“公司”的实例,因为另一个实例具有键值?

时间:2021-07-26 12:41:01

标签: .net-core entity-framework-core

我收到以下错误:

<块引用>

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

这是我的背景:

public class Context: DbContext
{
    public Context(DbContextOptions<Context> options) : base(options)
    {
        this.ChangeTracker.LazyLoadingEnabled = false;
    }
    public DbSet<Company> Companies { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        Seed.OnModelCreating(builder);
        //builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    }
}

存储库基础

public class EfRepository<T> : IAsyncRepository<T> where T : class
{
    protected readonly Context_context;

    public EfRepository(Context dbContext)
    {
        _context = dbContext;
    }

    public virtual T GetById(int id)
    {
        var keyValues = new object[] { id };
        return _context.Set<T>().Find(keyValues);
    }


    public List<T> List(Expression<Func<T, bool>> filter = null)
    {
        return filter != null ? _context.Set<T>().Where(filter).ToList() : _context.Set<T>().ToList();
    }

    public int Count(Expression<Func<T, bool>> filter = null)
    {
        return filter != null ? _context.Set<T>().Where(filter).Count() : _context.Set<T>().Count();
    }

    public void Add(T entity)
    {
        _context.Set<T>().Add(entity);
        _context.SaveChanges();
    }

    public void Update(T entity)
    {
        var model = _context.Entry(entity);
        model.State = EntityState.Modified;
        _context.SaveChanges();
    }

    public void Delete(T entity)
    {
        _context.Set<T>().Remove(entity);
        _context.SaveChanges();
    }

    public IQueryable<T> GetQueryable(Expression<Func<T, bool>> filter = null)
    {
        return filter == null
            ? _context.Set<T>()
            : _context.Set<T>().Where(filter);
    }

    public T Get(Expression<Func<T, bool>> filter = null)
    {
        return filter == null
            ? _context.Set<T>().FirstOrDefault()
            : _context.Set<T>().FirstOrDefault(filter);
    }

    public T GetWithoutTracking(Expression<Func<T, bool>> filter = null)
    {
        return filter == null
            ? _context.Set<T>().AsNoTracking().FirstOrDefault()
            : _context.Set<T>().AsNoTracking().FirstOrDefault(filter);
    }
    public EntityState GetEntityState(T entity)
    {
        return _context.Entry(entity).State;
    }
}
public class CompanyRepository : EfRepository<Company>, ICompanyRepository
{
    public CompanyRepository(Context dbContext) : base(dbContext)
    {}
    
    public IQueryable<CompanyDtoWithDetail> GetCompanyWithDetail(int? id = null)
    {
        return (from company in _context.Companies
                join companyType in _context.Definitions on company.CompanyTypeId equals companyType.Id
                join country in _context.Countries on company.CountryId equals country.Id
                join city in _context.Cities on company.CityId equals city.Id
                join district in _context.Districts on company.DistrictId equals district.Id
                join customerGroup in _context.Definitions on company.CustomerGroupId equals customerGroup.Id
                where id.HasValue ? company.Id == id : true
                select new CompanyDtoWithDetail(company, _context.AuthorizedPersons.OrderBy(person => person.Id).FirstOrDefault(person => person.CompanyId == company.Id), companyType.Name, country.Name, city.Name, district.Name, customerGroup.Name)
                      );
    }
}

业务层更新方法

[ValidationAspect(typeof(CompanyValidator), Priority = 1)]
public IDataResult<Company> Update(Company entity)
{
    var check = _companyRepository.GetWithoutTracking(x => x.Id == entity.Id);
    if (check == null)
        return new ErrorDataResult<Company>(Messages.CompanyNotFound);
    _companyRepository.Update(entity);
    var result = _companyRepository.GetCompanyWithDetail(entity.Id).FirstOrDefault();
    return new SuccessDataResult<Company>(result, Messages.CompanyUpdated);
}

API 控制器

[HttpPut("update")]
public IActionResult Update(Company company)
{
    var result = _companyService.Update(company);
    return StatusCode(result.GetStatusCode(), result);
}

Startup.cs

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
// Auto Mapper Configurations
services.AddSingleton(new MapperConfiguration(mc =>
{
    mc.AddProfile(new AutoMapperBusinessProfile());
}).CreateMapper());

services.AddDbContext<Context>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("Context"));
    options.EnableSensitiveDataLogging(true);
}
);

services.AddDependencyResolvers(new ICoreModule[]
{
    new CoreModule(),
});
public class CompanyDtoWithDetail : Company
{
    public string CompanyTypeName { get; set; }
    public string CountryName { get; set; }
    public string CityName { get; set; }
    public string DistrictName { get; set; }
    public string CustomerGroupName { get; set; }
    public AuthorizedPerson AuthorizedPerson { get; set; }
    public CompanyDtoWithDetail(Company company, AuthorizedPerson authorizedPerson, string companyTypeName, string countryName, string cityName, string districtName, string customerGroupName)
    {
        this.Id = company.Id;
        this.CompanyTypeId = company.CompanyTypeId;
        this.CompanyTypeName = companyTypeName;
        this.Code = company.Code;
        this.CommercialTitle = company.CommercialTitle;
        this.Explanation = company.Explanation;
        this.CountryId = company.CountryId;
        this.CountryName = countryName;
        this.CityId = company.CityId;
        this.CityName = cityName;
        this.DistrictId = company.DistrictId;
        this.DistrictName = districtName;
        this.Address = company.Address;
        this.ZipCode = company.ZipCode;
        this.CustomerGroupId = company.CustomerGroupId;
        this.CustomerGroupName = customerGroupName;
        this.IsInBlackList = company.IsInBlackList;
        this.AuthorizedPerson = authorizedPerson;
    }
}
[ValidationAspect(typeof(CompanyValidator), Priority = 1)]
public IDataResult<CompanyDtoWithDetail> Update(Company entity)
{
    var check = _companyRepository.GetWithoutTracking(x => x.Id == entity.Id);
    if (check == null)
        return new ErrorDataResult<CompanyDtoWithDetail>(Messages.CompanyNotFound);
    var entityState1 = _companyRepository.GetEntityState(entity); //Detached
    _companyRepository.Update(entity);
    var entityState2 = _companyRepository.GetEntityState(entity); // Unchanged
    var result = _companyRepository.GetCompanyWithDetail(entity.Id).FirstOrDefault();
    var entityState4 = _companyRepository.GetEntityState((Company)result); //Detached
    return new SuccessDataResult<CompanyDtoWithDetail>(result, Messages.CompanyUpdated);
}

1 个答案:

答案 0 :(得分:0)

您的实体状态在您调用时更改为已修改,然后是未更改

_companyRepository.Update(entity);

在此处查看带注释的实现:

public void Update(T entity)
{
    var model = _context.Entry(entity);
    // state is now Modified. This supercedes the AsNoTracking()
    model.State = EntityState.Modified;
    _context.SaveChanges();
    // state is now Unchanged here; it's now part of the tracking
}

然后当你打电话

var result = _companyRepository.GetCompanyWithDetail(entity.Id).FirstOrDefault();

您收到该错误是因为您试图将另一个实体(同一个)加载到上下文中,其中一个实体已存在且具有上述 未更改 状态。

更新后,您需要先分离实体(将其状态设置为分离

context.Entry(entity).State = EntityState.Detached;

或者您需要完全使用新的上下文。如何将其放入通用存储库取决于您。实际上,您没有遵循典型的工作单元模式 (source):

<块引用>

使用 Entity Framework Core (EF Core) 时的典型工作单元涉及(我的重点已添加):

  • 创建 DbContext 实例 - 通过上下文跟踪实体实例。实体被跟踪
    • 从查询中返回
    • 被添加或附加到上下文
  • 根据需要对被跟踪实体进行更改以实施业务规则
  • SaveChanges 或 SaveChangesAsync 被调用。 EF Core 检测所做的更改并将其写入数据库。
  • DbContext 实例被释放

当您在 DbContext 之后使用 SaveChanges 时,上下文仍然是“活动的”,因为它的当前状态仍然是活动的;如果您执行了与当前状态不兼容的操作,例如尝试再次加载同一实体,则会出现错误。

使用 DbContextFactory

上面引用的来源建议您use a DbContext factory 当您的工作范围与 DbContext 生命周期不一致时。您的案例符合条件,因为您在 SaveChanges 之后工作。

资源

总而言之,您有 3 个选择

  • 在保存更改之后和下次阅读之前分离实体
  • 使用与注入工厂不同的上下文
  • 更改您的访问模式以不重新阅读