.Net Core 3.1 - 使用自定义日期跟踪器时,无法跟踪实体类型"City"的实例



我知道类似的问题已经被问了很多次,但不幸的是,经过几天的研究,仍然无法找到解决我问题的方法。我试图详细解释问题是什么。以下是一个简单的项目,可以重现问题!

问题:

我使用自定义日期跟踪器,这样我不仅可以在保存实体时添加创建日期或更新日期,还可以软删除我的记录。

然而,当我使用这个时,我会得到以下错误

System.InvalidOperationException: 'The instance of entity type 'City' cannot be tracked because another instance with the key value '{Id: a6606535-76a5-4a23-b204-08d882481a95}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'

奇怪的是,这种情况只会偶尔发生,我想说,当我第一次运行项目时,90%的时间都会发生,但如果我绕过异常并达到终点,那么只有10%的时间会发生。

代码:

型号:

public class CitySync : ICreatedDateTracking
{
public long Id { get; set; }
public Guid CityId { get; set; }
public virtual City City { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public int Added { get; set; }
}
public partial class School : IDateTracking
{
public string Id { get; set; }
public string Name { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public virtual City City { get; set; }
public Guid CityId { get; set; }
public DateTimeOffset? DeletedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
}
public partial class City : IDateTracking
{
public Guid Id { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public string OwnerName { get; set; }
public virtual List<School> Schools { get; set; }
public virtual ICollection<CitySync> CitySync { get; set; }
public DateTimeOffset? DeletedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
}

DbContext:


public class ApplicationDbContext : DbContext
{

private readonly IChangeTracker _changeTracker;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IChangeTracker changeTracker)
: base(options)
{
_changeTracker = changeTracker;
}
public void DetachAllEntities()
{
var changedEntriesCopy = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.ToList();
foreach (var entry in changedEntriesCopy)
entry.State = EntityState.Detached;
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
Task.Run(async () => await _changeTracker.BeforeSaveChanges(this));
var saveChanges = base.SaveChanges(acceptAllChangesOnSuccess);
Task.Run(async () => await _changeTracker.AfterSaveChanges(this));
return saveChanges;
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
await _changeTracker.BeforeSaveChanges(this);
var saveChangesAsync = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
await _changeTracker.AfterSaveChanges(this);
return saveChangesAsync;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<School>(entity =>
{
entity.Property(e => e.Id).HasMaxLength(50);
entity.Property(e => e.Name).HasMaxLength(255);
entity.HasOne(x => x.City).WithMany(x => x.Schools);
entity.HasKey(x => new
{
x.Id,
x.CitytId
});
});
modelBuilder.Entity<City>(entity =>
{
entity.Property(e => e.OwnerName).HasMaxLength(255);
entity.HasMany(x => x.Schools).WithOne(x => x.City);
});
modelBuilder.Entity<CitySync>(entity =>
{
entity.HasOne(x => x.City).WithMany(x => x.CitySync);
});
foreach (var entityType in modelBuilder.Model.GetEntityTypes().Where(e => typeof(IDateTracking).IsAssignableFrom(e.ClrType)))
{
if (entityType.BaseType == null)
{
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(ConvertFilterExpression<IDateTracking>(e => e.DeletedDate == null, entityType.ClrType));
}
}
}
private static LambdaExpression ConvertFilterExpression<TInterface>(Expression<Func<TInterface, bool>> filterExpression, Type entityType)
{
var newParam = Expression.Parameter(entityType);
var newBody = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), newParam, filterExpression.Body);
return Expression.Lambda(newBody, newParam);
}
public virtual DbSet<School> Schools { get; set; }
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<CitySync> CitySync { get; set; }
}

DateChangeTracker:


public class DateChangeTracker : IChangeTracker
{
public static EntityState[] AuditedEntityStates = { EntityState.Added, EntityState.Modified, EntityState.Deleted };
public DateChangeTracker()
{
}
public virtual Task BeforeSaveChanges(DbContext dbContext)
{
var now = DateTimeOffset.UtcNow;
foreach (var entry in dbContext.ChangeTracker.Entries().Where(e => AuditedEntityStates.Contains(e.State)).ToList())
{
if (entry.Entity is ICreatedDateTracking createdDateTracking)
{
if (entry.State == EntityState.Added)
{
createdDateTracking.CreatedDate = now;
}
}
if (entry.Entity is IUpdatedCreatedDateTracking updatedCreatedDateTracking)
{
updatedCreatedDateTracking.UpdatedDate = now;
}
if (entry.Entity is IDateTracking dateTracking)
{
if (entry.State == EntityState.Added)
{
dateTracking.CreatedDate = now;
}
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
dateTracking.DeletedDate = now;
}
}
}
return Task.CompletedTask;
}
public virtual Task AfterSaveChanges(DbContext dbContext)
{
return Task.CompletedTask;
}
}

DbContext工厂:


public class ApplicationDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new ApplicationDbContext(builder.Options, new DateChangeTracker());
}
}
  • IDate接口,实现创建、更新、删除的文件

学校服务:


public class SchoolService : ISchoolService
{
private readonly ApplicationDbContext applicationDbContext;

public SchoolService(ApplicationDbContext applicationDbContext)
{
this.applicationDbContext = applicationDbContext;
}
public Guid PopulateCity()
{
var existingCity = applicationDbContext.Cities.FirstOrDefault();
if (existingCity == null)
{
var city = new Models.City { CreatedDate = DateTimeOffset.Now, OwnerName = "test" };
applicationDbContext.Cities.Add(city);
applicationDbContext.SaveChanges();
return city.Id;
}
return existingCity.Id;

}

public void ProcessSchools(Guid cityId)
{
var city = applicationDbContext.Cities.FirstOrDefault(x => x.Id == cityId);
if (city == null)
throw new Exception("City doesnt exist");
var citySync = new CitySync
{
CityId = city.Id,
CreatedDate = DateTimeOffset.Now,
};
var existingSchools = applicationDbContext.Schools.Where(x => x.CitytId == cityId).ToList();
// update schools if the exists 
// add new ones if they dont
var schools = new List<School>();
if (!existingSchools.Any())
{
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "1", Name = "school1" });
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "2", Name = "school2" });
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "3", Name = "school3" });
}
else
{
foreach (var item in existingSchools)
{
item.UpdatedDate = DateTimeOffset.Now;
}
applicationDbContext.SaveChanges();
}
var additions = schools.Except(existingSchools, new SchoolComparer()).ToList();
foreach (var school in additions)
{
school.CitytId = city.Id;
}
applicationDbContext.Schools.AddRange(additions);
city.UpdatedDate = DateTimeOffset.Now;
city.OwnerName = "Updated Name";
citySync.Added = additions.Count;
applicationDbContext.CitySync.Add(citySync);
applicationDbContext.SaveChanges();
}
}
public class SchoolComparer : IEqualityComparer<School>
{
public bool Equals(School x, School y)
{
return x?.Id == y?.Id;
}
public int GetHashCode(School obj)
{
return 0;
}
}

错误发生在以下行

*applicationDbContext.CitySync.Add(citySync);*

启动:


public class Startup
{
public Startup(IConfiguration configuration, IHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IChangeTracker), typeof(DateChangeTracker));
services.AddTransient<ISchoolService, SchoolService>();
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(connectionString); options.EnableSensitiveDataLogging(); }, ServiceLifetime.Scoped);
services.AddControllers();
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
}
}
}

请注意,只有当我使用日期跟踪器时才会发生这种情况,如果我不使用覆盖的saveChanges方法,那么一切都会正常工作。

请尝试使用这种方式

services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(connectionString,x => x.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)),ServiceLifetime.Transient);
services.AddTransient<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

相关内容

最新更新