问题描述
我正在使用EF Core 3.1和内存数据库开发一些API。
以下代码存在一些问题。我不能为重复的Name和同一表中不存在的外键生成异常。
public class Category
{
public Guid Id { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public Guid? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual IList<Category> Children { get; set; }
}
public class ItemDbContext : DbContext
{
public ItemDbContext(DbContextOptions<ItemDbContext> options)
: base(options) {}
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("s_items");
GetCategoryBuilder(modelBuilder);
base.OnModelCreating(modelBuilder);
}
private void GetCategoryBuilder(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>(
entity =>
{
entity.Property(c => c.Id)
.Isrequired()
.HasColumnName("CATE_ID")
.HasMaxLength(40);
entity.Property(c => c.Name)
.Isrequired()
.IsFixedLength(false)
.IsUnicode()
.HasColumnName("CATE_NAME")
.HasMaxLength(50);
entity.Property(c => c.CreationDate)
.Isrequired()
.HasColumnName("CATE_CREATION_DATE")
.HasDefaultValuesql("CURRENT_TIMESTAMP")
.ValueGeneratedOnAdd();
entity.Property(c => c.LastModifiedDate)
.Isrequired(false)
.HasColumnName("CATE_UPDATE_DATE")
.HasDefaultValuesql("CURRENT_TIMESTAMP")
.ValueGeneratedOnUpdate();
entity.Property(c => c.IsActive)
.HasColumnName("CATE_ACTIVE")
.HasDefaultValue(true)
.ValueGeneratedOnAddOrUpdate();
entity.Property(c => c.ParentId)
.IsFixedLength(true)
.HasColumnName("CATE_PARENT_ID")
.HasMaxLength(40);
}
);
modelBuilder.Entity<Category>()
.HasOne(c => c.Parent)
.WithMany(c => c.Children)
.HasForeignKey(c => c.ParentId)
.HasConstraintName("FK_CATE_PARENT");
modelBuilder.Entity<Category>()
.ToTable("Categories")
.HasKey(c => c.Id)
.HasName("PK_CATE");
modelBuilder.Entity<Category>()
.HasIndex(u => u.Name)
.IsUnique(true)
.HasName("UK_CATE_NAME");
}
}
public class CategoryRepository : ICategoryRepository
{
private readonly ItemDbContext _context;
public CreateCategoryRepository(ItemDbContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
_context = context;
}
public Category Create(Category item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
Category result = null;
try
{
_context.Categories.Add(item);
int nbrowsImpacted = _context.SaveChanges();
if (nbrowsImpacted == 1)
{
result = item;
}
}
catch (InvalidOperationException ex)
{
var message = "The instance of entity type 'Category' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities,ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.";
if (ex.Message == message)
{
throw new DBConcurrencyException("There is already exists a similar category");
}
}
catch (ArgumentException ex)
{
var message = "An item with the same key has already been added.";
if (ex.Message.StartsWith(message))
{
throw new DBConcurrencyException("There is already exists a similar category");
}
}
return result;
}
}
[TestClass]
public class CategoryRepository
{
[TestMethod]
[TestCategory("Category_Repository")]
public void Category_Create_NotExistantParentId()
{
#region Arrange
IServiceProvider provider = GetServiceProvider(
DatabaseType.InMemory,injectCreateCategoryRepository: true);
ItemDbContext context = provider.GetrequiredService<ItemDbContext>();
IList<Product> products = SeedInMemory.GetProducts();
context.Products.AddRange(products);
context.SaveChanges();
Category category = new Category()
{
Id = CategoryId.New(),Name = "Name",CreationDate = DateTime.UtcNow,IsActive = true,ParentId = CategoryId.New()
};
ICategoryRepository repository = provider.GetrequiredService<ICategoryRepository>();
var message = "There is already exists a similar category";
#endregion
#region Assert
var result = Assert.ThrowsException<DBConcurrencyException>(() => repository.Create(category));
Assert.AreEqual(message,result.Message);
#endregion
}
[TestMethod]
[TestCategory("CreateCategory_Repository")]
public void CreateCategory_Create_DuplicateName()
{
#region Arrange
IServiceProvider provider = GetServiceProvider(
DatabaseType.InMemory,Name = context.Categories.FirstOrDefault().Name,IsActive = true
};
ICategoryRepository repository = provider.GetrequiredService<ICategoryRepository>();
var message = "There is already exists a similar category";
#endregion
#region Assert
var result = Assert.ThrowsException<DBConcurrencyException>(() => repository.Create(category));
Assert.AreEqual(message,result.Message);
#endregion
}
}
// Part for GetServiceProvider
services.AddDbContext<ItemDbContext>(
options =>
{
options.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.DetachedLazyLoadingWarning));
options.UseLazyLoadingProxies().UseInMemoryDatabase("Test");
});
对于重复的主键,我有一个ArgumentException
。
两个测试用例都不会引发异常,nbrowsImpacted变量等于1。为什么?
此致, @R_502_6460@i680
解决方法
我不知道您为什么认为_context.SaveChanges
会引发异常InvalidOperationException
或ArgumentException
。我调查了documentation,发现在这种情况下可以期望DbUpdateException
和DbUpdateConcurrencyException
。在测试中,您仅添加一个元素,因此不会出现唯一的键冲突。也许在种子方法中很有趣,但是您没有粘贴它。