API Proposal: `HasOnDeleteSetNullProperties()` to support Postgres 15+ `ON DELETE SET NULL (column_a, column_b)` column lists
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 45/100
Hướng nghiên cứu
Start by reading the Npgsql annotation provider and migrations SQL generator, especially their foreign-key constraint handling; the issue’s prototype shows the intended integration points but does not name repository file paths or tests. Check existing tests for foreign-key migration SQL and annotation handling, then add coverage for the proposed property- and column-based APIs. Done means generated migrations include the selected column list for SET NULL or SET DEFAULT.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Preface
PostgreSQL 15+ added support for specifying a list of columns after a ON DELETE SET NULL or ON DELETE SET DEFAULT constraint, which specifies which subset of a composite foreign key's columns to set to NULL when the principal is deleted, instead of all columns in the foreign key.
The DDL constraints documentation states:
The actions SET NULL and SET DEFAULT can take a column list to specify which columns to set. Normally, all columns of the foreign-key constraint are set; setting only a subset is useful in some special cases.
For example:
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id);
Npgsql does not yet support a built-in way to target specific SET NULL columns using the standard Fluent API.
Proposed API
An extension method for ReferenceCollectionBuilder named HasOnDeleteSetNullColumns() for specifying raw column names, and HasOnDeleteSetNullProperties() for the property names, with overloads for string array and lambda expression:
public static ReferenceCollectionBuilder HasOnDeleteSetNullColumns(
this ReferenceCollectionBuilder builder,
params string[] setNullColumnNames)
public static ReferenceCollectionBuilder HasOnDeleteSetNullProperties(
this ReferenceCollectionBuilder builder,
params string[] setNullPropertyNames)
public static ReferenceCollectionBuilder<TPrincipalEntity, TDependentEntity> HasOnDeleteSetNullProperties<TPrincipalEntity, TDependentEntity>(
this ReferenceCollectionBuilder<TPrincipalEntity, TDependentEntity> builder,
Expression<Func<TDependentEntity, object?>> setNullPropertiesExpression)
where TPrincipalEntity : class
where TDependentEntity : class
Example usage
modelBuilder.Entity<Author>()
.HasMany(a => a.Posts)
.WithOne(p => p.Author)
.HasPrincipalKey(a => new { a.TenantId, a.Id })
.HasForeignKey(p => new { p.TenantId, p.AuthorId })
.OnDelete(DeleteBehavior.SetNull)
.HasOnDeleteSetNullProperties(p => p.AuthorId);
// or .HasOnDeleteSetNullProperties("AuthorId");
// or .HasOnDeleteSetNullColumns("author_id");
Workarounds
Current workarounds include manually modifying the migrations with custom SQL to drop the initial constraint and re-add it with the column list, or to manually extend the migration generator. The latter being the path I took to prototype an implementation of this proposal, which may serve as the basis for a future PR.
This prototype implements:
- Two custom annotations:
- "CustomNpgsql:OnDeleteSetNull:PropertyNames" is the property name list
- "CustomNpgsql:OnDeleteSetNull:ColumnNames" is the actual column name list
ReferenceCollectionBuilderextension methods- Adds the annotations to the Foreign Key Constraint
- Derived
NpgsqlAnnotationProvider- Translates "CustomNpgsql:OnDeleteSetNull:PropertyNames" into concrete column names and re-adds them into the relational model as "CustomNpgsql:OnDeleteSetNull:ColumnNames"
- Yields existing "CustomNpgsql:OnDeleteSetNull:ColumnNames" to the relational model
- Derived
MigrationsSqlGenerator- Adds on the additional column list SQL during ForeignKeyConstraint generation
Extension methods
public static ReferenceCollectionBuilder HasOnDeleteSetNullColumns(
this ReferenceCollectionBuilder builder,
params string[] setNullColumnNames)
{
builder.HasAnnotation(
"CustomNpgsql:OnDeleteSetNull:ColumnNames",
setNullColumnNames);
return builder;
}
public static ReferenceCollectionBuilder HasOnDeleteSetNullProperties(
this ReferenceCollectionBuilder builder,
params string[] setNullPropertyNames)
{
builder.Metadata.SetAnnotation(
"CustomNpgsql:OnDeleteSetNull:PropertyNames",
setNullPropertyNames);
return builder;
}
public static ReferenceCollectionBuilder<TPrincipalEntity, TDependentEntity> HasOnDeleteSetNullProperties<TPrincipalEntity, TDependentEntity>(
this ReferenceCollectionBuilder<TPrincipalEntity, TDependentEntity> builder,
Expression<Func<TDependentEntity, object?>> setNullPropertiesExpression)
where TPrincipalEntity : class
where TDependentEntity : class
{
string[] setNullPropertyNames = setNullPropertiesExpression
.GetMemberAccessList()
.Select(member => member.Name)
.ToArray();
builder.Metadata.SetAnnotation(
"CustomNpgsql:OnDeleteSetNull:PropertyNames",
setNullPropertyNames);
return builder;
}
Custom NpgsqlAnnotationProvider
public class CustomNpgsqlAnnotationProvider : NpgsqlAnnotationProvider
{
public CustomNpgsqlAnnotationProvider(RelationalAnnotationProviderDependencies dependencies)
: base(dependencies)
{
}
public override IEnumerable<IAnnotation> For(IForeignKeyConstraint foreignKeyConstraint, bool designTime)
{
foreach(IAnnotation baseAnnotion in base.For(foreignKeyConstraint, designTime)) {
yield return baseAnnotion;
}
foreach (IForeignKey thisForeignKey in foreignKeyConstraint.MappedForeignKeys)
{
if (thisForeignKey.FindAnnotation("CustomNpgsql:OnDeleteSetNull:ColumnNames")?.Value is string[] columnNames)
{
yield return new Annotation(
"CustomNpgsql:OnDeleteSetNull:ColumnNames",
columnNames
);
}
// Translate CustomNpgsql:OnDeleteSetNull:PropertyNames -> CustomNpgsql:OnDeleteSetNull:ColumnNames
if (thisForeignKey.FindAnnotation("CustomNpgsql:OnDeleteSetNull:PropertyNames")?.Value is string[] propertyNames)
{
// Translate property names to column names using the set of columns from the foreign key constraint
string[] columnNames = propertyNames
.SelectMany(p =>
foreignKeyConstraint.Columns
.Where(c => c.PropertyMappings.Any(pm => pm.Property.Name == p))
)
.Select(c => c.Name)
.ToArray();
yield return new Annotation(
"CustomNpgsql:OnDeleteSetNull:ColumnNames",
columnNames
);
}
}
}
}
Custom NpgsqlMigrationsSqlGenerator
public class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator {
protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder)
{
// Write the constraint as standard. This is not yet terminated.
base.ForeignKeyConstraint(operation, model, builder);
// Only append column list to SET NULL or SET DEFAULT
if (operation.OnDelete is ReferentialAction.SetNull or ReferentialAction.SetDefault
&& operation?.FindAnnotation("CustomNpgsql:OnDeleteSetNull:ColumnNames")?.Value is string[] columnNames && columnNames.Length > 0)
{
// Append the column list
builder.Append(" (");
builder.Append(ColumnList(columnNames));
builder.Append(")");
}
}
}
- Ngôn ngữ chính
- C#
- Star
- 1.8k
- Fork
- 268
- Merge trung bình
- 2 giờ 35 phút
- Pull request đã merge (30 ngày)
- 136
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của npgsql/efcore.pg
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
NTS: the M ordinate is dropped from geometry SQL literals and from geometries stored in JSONCó thể đã có người làm @Ahmed7991 đã nhận 5 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
npgsql/efcore.pg#4093 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`TimeSpan`/`Duration` component members: negative values are floored, and intervals computed in SQL are read field by fieldCó thể đã có người làm @jeremy-morren đã nhận 11 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Numeric storage parameters are formatted with the current culture, so migrations emit 0,01 instead of 0.01Có thể đã có người làm @Sorbitol đã nhận 12 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 30/100
Maintainer thường phản hồi trong vòng 1 ngày
-
`Contains` on a JSON primitive collection compares JSON text, so `DateTime` and `TimeOnly` items never matchCó thể đã có người làm @Mr444-dev đã nhận 7 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
npgsql/efcore.pg#4099 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của npgsql/efcore.pg
Issue tương tự
-
Issue-Bug Needs-Triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
JiaFeiMiao-K-Cat/DiscImageStudio#281 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area/access-management Frontend onboarding candidate :handshake:
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
Altinn/altinn-auth#4941 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
aws/aws-sdk-net#4586 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Kryptos-FR/MarkView.Avalonia#269 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày