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

问题描述 投票:0回答:2

我收到以下错误:

无法跟踪实体类型“Company”的实例,因为已跟踪具有键值“{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);
}

启动.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);
}
.net-core entity-framework-core
2个回答
10
投票

您的实体状态更改为 Modified,然后在您调用时更改为 Unchanged

_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();

您收到该错误是因为您尝试将另一个实体(相同的实体)加载到上下文中,其中已经存在具有上述 Unchanged 状态的实体。

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

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

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

使用 Entity Framework Core (EF Core) 时的典型工作单元涉及(我添加了强调):

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

当您在

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

使用 DbContextFactory

上面引用的来源建议您在工作范围与 DbContext 生命周期不一致时使用 DbContext 工厂。您的案例符合资格,因为您在 SaveChanges

 之后工作。

资源

  • https://www.tektutorialshub.com/entity-framework-core/change-tracker-entity-states-in-entity-framework-core/
  • https://learn.microsoft.com/en-us/ef/core/change-tracking/
总结来说,你有3个选择

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

0
投票
我的解决方案:

    我到处都删除了 AsNoTracking()
  • 我删除了将 QueryTrackingBehaviour 设置为“无跟踪”
  • 我保存实体
  • 没有context.Update(),只是查询后修改实体,然后调用context.SaveChanges()
© www.soinside.com 2019 - 2024. All rights reserved.