diff --git a/DbExceptionClassifier/PostgreSQL/PostgreSQLExceptionClassifier.cs b/DbExceptionClassifier/PostgreSQL/PostgreSQLExceptionClassifier.cs index 7fd52cc..590ceda 100644 --- a/DbExceptionClassifier/PostgreSQL/PostgreSQLExceptionClassifier.cs +++ b/DbExceptionClassifier/PostgreSQL/PostgreSQLExceptionClassifier.cs @@ -6,7 +6,7 @@ namespace DbExceptionClassifier.PostgreSQL; public class PostgreSQLExceptionClassifier : IDbExceptionClassifier { - public bool IsReferenceConstraintError(DbException exception) => exception is PostgresException { SqlState: PostgresErrorCodes.ForeignKeyViolation }; + public bool IsReferenceConstraintError(DbException exception) => exception is PostgresException { SqlState: PostgresErrorCodes.ForeignKeyViolation or PostgresErrorCodes.RestrictViolation }; public bool IsCannotInsertNullError(DbException exception) => exception is PostgresException { SqlState: PostgresErrorCodes.NotNullViolation }; public bool IsNumericOverflowError(DbException exception) => exception is PostgresException { SqlState: PostgresErrorCodes.NumericValueOutOfRange }; public bool IsUniqueConstraintError(DbException exception) => exception is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation }; diff --git a/EntityFramework.Exceptions/Tests/DatabaseTests.cs b/EntityFramework.Exceptions/Tests/DatabaseTests.cs index 97317a7..68edab7 100644 --- a/EntityFramework.Exceptions/Tests/DatabaseTests.cs +++ b/EntityFramework.Exceptions/Tests/DatabaseTests.cs @@ -341,6 +341,53 @@ await DemoContext.Products } } + [Fact] + public virtual async Task DeleteParentItemWithRestrictThrowsReferenceConstraintException() + { + var product = new Product { Name = "AN3" }; + var productReview = new ProductReview { Product = product, Comment = "Great" }; + DemoContext.ProductReviews.Add(productReview); + await DemoContext.SaveChangesAsync(); + + CleanupContext(); + + product = DemoContext.Products.Find(product.Id); + DemoContext.Products.Remove(product); + + Assert.Throws(() => DemoContext.SaveChanges()); + await Assert.ThrowsAsync(() => DemoContext.SaveChangesAsync()); + } + + [Fact] + public virtual async Task DeleteParentItemWithRestrictThrowsReferenceConstraintExceptionThroughExecuteDelete() + { + var product = new Product { Name = "AN4" }; + var productReview = new ProductReview { Product = product, Comment = "Great" }; + DemoContext.ProductReviews.Add(productReview); + await DemoContext.SaveChangesAsync(); + + CleanupContext(); + + Assert.Throws(Query); + await Assert.ThrowsAsync(QueryAsync); + + return; + + void Query() + { + DemoContext.Products + .Where(p => p.Name == "AN4") + .ExecuteDelete(); + } + + async Task QueryAsync() + { + await DemoContext.Products + .Where(p => p.Name == "AN4") + .ExecuteDeleteAsync(); + } + } + [Fact] public async Task NotHandledViolationReThrowsOriginalException() { diff --git a/EntityFramework.Exceptions/Tests/DemoContext.cs b/EntityFramework.Exceptions/Tests/DemoContext.cs index d59b700..b3a6bcf 100644 --- a/EntityFramework.Exceptions/Tests/DemoContext.cs +++ b/EntityFramework.Exceptions/Tests/DemoContext.cs @@ -12,6 +12,7 @@ public class DemoContext : DbContext public DbSet Products { get; set; } public DbSet ProductSales { get; set; } public DbSet ProductPriceHistories { get; set; } + public DbSet ProductReviews { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -21,6 +22,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity().Property(b => b.Price).HasColumnType("decimal(5,2)").IsRequired(); builder.Entity().Property(p => p.EffectiveDate).IsRequired(); builder.Entity().HasOne(p => p.Product).WithMany().OnDelete(DeleteBehavior.NoAction); + builder.Entity().HasOne(p => p.Product).WithMany().OnDelete(DeleteBehavior.Restrict); } public const int ProductNameMaxLength = 25; @@ -50,6 +52,14 @@ public class ProductPriceHistory public Product Product { get; set; } } +public class ProductReview +{ + public int Id { get; set; } + public string Comment { get; set; } + public int ProductId { get; set; } + public Product Product { get; set; } +} + public class Customer { public int Id { get; set; } diff --git a/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs b/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs index c152848..cc82a59 100644 --- a/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs +++ b/EntityFramework.Exceptions/Tests/PostgreSQLTests.cs @@ -16,7 +16,7 @@ public class PostgreSQLDemoContextFixture : DemoContextFixture BuildDemoContextOptions(DbContextOptionsBuilder builder, string connectionString) diff --git a/EntityFramework.Exceptions/Tests/SqliteTests.cs b/EntityFramework.Exceptions/Tests/SqliteTests.cs index 1d6c6a1..d435123 100644 --- a/EntityFramework.Exceptions/Tests/SqliteTests.cs +++ b/EntityFramework.Exceptions/Tests/SqliteTests.cs @@ -55,6 +55,18 @@ public override Task NumericOverflowViolationThrowsNumericOverflowExceptionThrou return Task.CompletedTask; } + [Fact(Skip = "Skipping as SQLite reports RESTRICT violations with SQLITE_CONSTRAINT_TRIGGER instead of SQLITE_CONSTRAINT_FOREIGNKEY")] + public override Task DeleteParentItemWithRestrictThrowsReferenceConstraintException() + { + return Task.CompletedTask; + } + + [Fact(Skip = "Skipping as SQLite reports RESTRICT violations with SQLITE_CONSTRAINT_TRIGGER instead of SQLITE_CONSTRAINT_FOREIGNKEY")] + public override Task DeleteParentItemWithRestrictThrowsReferenceConstraintExceptionThroughExecuteDelete() + { + return Task.CompletedTask; + } + [Fact] public override async Task Deadlock() {