为什么没有主体实体的依赖实体在插入时不会抛出外键错误?

发布于 2025-01-18 11:01:48 字数 7367 浏览 1 评论 0原文

我正在使用EF 6以及属性和流利的API的混合物来映射我必须读/写入的旧数据库。我已经完成了Fluent API的关系映射。

我还使每个实体都从一个抽象的基础上继承,因此我可以以更抽象的方式与实体进行交互。

基类:

public abstract class BaseClass {

}

无论出于何种原因,我都需要与表格列与表格的主要键不同的位置进行交互。我也无法更改基础数据库中表的结构,因为我无法控制的许多其他应用程序使用。

我有几种不同类型的主体依赖性的一对多关系定义,每个受抚养人都需要一个主体,并且本金具有许多可选的依赖者。

例如,所讨论的类看起来都是这样的:

[Table("PrincipalTable")]
public class Principal : BaseClass {
    
    public int TableId { get; set; } // this is the identity column

    [Key]
    public string PrincipalId { get; set; }

    // more properties

    public virtual ICollection<Dependent> Dependents { get; set; }
}

[Table("DependentTable")]
public class Dependent : BaseClass {

    [Key]
    public int DependentId { get; set; }
    
    [Required]
    public string PrincipalId { get; set; }

    // more properties

    public virtual Principal Principal { get; set; }
}

这些对的每对的外键关系在流利的API中定义如下:

modelBuilder.Entity<Principal>()
    .HasMany(e => e.Dependents)
    .WithRequired(e => e.Principal)
    .HasForeignKey(e => e.PrincipalId)
    .WillCascadeOnDelete(true);

当获取类型主体的记录时,它也可以正确返回其依赖者任何。到目前为止,一切都很好。

但是,我还需要防止在没有有效的原理记录的情况下添加任何依赖。这似乎无法正常工作。

添加方法:

public async Task<bool> Add<T>(T entity, Type entityType) where T : class
{
    try
    {
        var contextSet = context.Set(entityType);
        contextSet.Add(entity);
                
        var result = await context.SaveChangesAsync();

        return result > 0;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

在测试此问题时,我可以添加一个完全伪造的princionalid的依赖记录,并且没有抛出外键错误。

“类型entityType”参数是必要的,因为t直到运行时才知道,并且使用通用“ context.set()”返回类型的baseclass类型,而不是所需的派生实体类型。我已经确认“ context.set(entityType)”通过踏入代码来返回正确的DBSET。

无论主要主键是字符串,int,小数还是复合键,这似乎都会发生这种情况。

编辑(4/4/22):

作为一个例子,这是所讨论的实体之一:

[Table("hdepben")]
public class SageHrDependentBenefit : SageHrEntityBase
{
    [Key]
    [Column("id_col", Order = 0)]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int id_col { get; set; }

    [Column("d_benecode", Order = 1)]
    [StringLength(10)]
    [Required]
    public string d_benecode { get; set; }

    [Column("d_chadate")]
    public DateTime? d_chadate { get; set; }

    [Column("d_company")]
    [StringLength(3)]
    [Required]
    public string d_company { get; set; }

    [Column("d_coverage")]
    [Required]
    public decimal d_coverage { get; set; }

    [Column("d_depid", Order = 5)]
    [Required]
    public decimal d_depid { get; set; }

    [Column("d_effdate")]
    public DateTime? d_effdate { get; set; }

    [Column("d_empno", Order = 7)]
    [StringLength(9)]
    [Required]
    public string SageId { get; set; }

    [Column("d_expdate")]
    public DateTime? d_expdate { get; set; }

    [Column("d_misc1")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc1 { get; set; }

    [Column("d_misc2")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc2 { get; set; }

    [Column("d_misc3")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc3 { get; set; }

    [Column("d_misc4")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc4 { get; set; }

    [Column("d_premium")]
    [Required]
    public decimal d_premium { get; set; }

    [Column("d_comments")]
    [Required(AllowEmptyStrings = true)]
    public string d_comments { get; set; }

    [Column("d_courtord")]
    [StringLength(1)]
    [Required(AllowEmptyStrings = true)]
    public string d_courtord { get; set; }

    [Column("d_covfrom")]
    public DateTime? d_covfrom { get; set; }

    [Column("d_covto")]
    public DateTime? d_covto { get; set; }

    [Column("d_ordnumb")]
    [StringLength(25)]
    [Required(AllowEmptyStrings = true)]
    public string d_ordnumb { get; set; }

    [Column("d_crtnotes")]
    [Required(AllowEmptyStrings = true)]
    public string d_crtnotes { get; set; }

    [Column("lckuser")]
    [StringLength(4)]
    public string lckuser { get; set; }

    [Column("lckstat")]
    [StringLength(1)]
    public string lckstat { get; set; }

    [Column("lckdate")]
    public DateTime? lckdate { get; set; }

    [Column("lcktime")]
    [StringLength(8)]
    public string lcktime { get; set; }

    public virtual SageHrDependent Dependent { get; set; }

    public virtual SageHrBenefits Benefit { get; set; }
}

这是流利的API中的外键映射:

modelBuilder.Entity<SageHrDependent>()
    .HasMany(e => e.Benefits)
    .WithRequired(e => e.Dependent)
    .HasForeignKey(e => e.d_depid)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<SageHrBenefits>()
    .HasMany(e => e.DependentBenefits)
    .WithRequired(e => e.Benefit)
    .HasForeignKey(e => new { e.d_benecode, e.SageId })
    .WillCascadeOnDelete(true);

此依赖性宾语实体是外键映射的,一个因实体和福利实体。

所讨论的测试涉及将DTO张贴到Web API端点,该端点将与此数据库的所有交互结合在一起。一旦发布,DTO就会映射到实体,如下所示:

public static SageHrDependentBenefit MapToSage(this DependentBenefitDTO dto)
{
    return dto == null ? null : new SageHrDependentBenefit()
    {
        d_benecode = dto.BenefitCode ?? "",
        d_chadate = dto.ChangeDate,
        d_comments = dto.Comments ?? "",
        d_company = dto.Company ?? "",
        d_courtord = dto.d_courtord ?? "",
        d_coverage = dto.CoverageAmount,
        d_covfrom = dto.CoveredFrom,
        d_covto = dto.CoveredTo,
        d_crtnotes = dto.crtnotes ?? "",
        d_depid = dto.DependentId,
        d_effdate = dto.EffectiveDate,
        d_expdate = dto.ExpirationDate,
        id_col = dto.TableId,
        lckdate = dto.lckdate,
        lckstat = dto.lckstat,
        lcktime = dto.lcktime,
        lckuser = dto.lckuser,
        d_misc1 = dto.Misc1 ?? "",
        d_misc2 = dto.Misc2 ?? "",
        d_misc3 = dto.Misc3 ?? "",
        d_misc4 = dto.Misc4 ?? "",
        d_ordnumb = dto.OrderNumber ?? "",
        d_premium = dto.DependentPremium,
        SageId = dto.EmployeeId ?? ""
   };
}

不良数据发布到Web服务(应该抛出外国密钥错误):

public static DependentBenefitDTO GetBadNoBeneDummyDependentBenefit(int dependentId)
{
    return new DependentBenefitDTO()
    {
        BenefitCode = "BADDATA",
        ChangeDate = new DateTime(2022, 01, 03),
        Company = "TEC",
        DependentId = dependentId,
        EffectiveDate = new DateTime(2021, 08, 01),
        ExpirationDate = new DateTime(2099, 12, 31),
        EmployeeId = GetDummyEmployeeId()
    };
}

public static DependentBenefitDTO GetBadNoDepDummyDependentBenefit()
{
    return new DependentBenefitDTO()
    {
        BenefitCode = "INH3000",
        ChangeDate = new DateTime(2022, 01, 03),
        Company = "TEC",
        DependentId = 10000,
        EffectiveDate = new DateTime(2021, 08, 01),
        ExpirationDate = new DateTime(2099, 12, 31),
        EmployeeId = "123456"
    };
}

没有“ baddata”的福利代码,也没有依赖性实体,也没有依赖性代码但是,依赖性为10000的实体。但是,没有福利父实体的数据,也不是没有依赖父实体的数据会引发外键错误。

如前所述,当从相同的DB/上下文中获取数据时,所有预期的实体都是正确关联并返回的。

逐步浏览代码,我已经确认映射也正确分配了值。

结束编辑4/4/22

为什么在插入物上不会丢下错误?

I'm using EF 6 and a mix of attributes and Fluent API to map a legacy database that I have to read/write to. I've done the relationship mapping in Fluent API.

I have also made each entity inherit from an abstract BaseClass, so that I can interact with the entities in a more abstract way.

The base class:

public abstract class BaseClass {

}

For whatever reason, there are number of tables I need to interact with where the table identity column is not the same column as the primary key for the table. I also can't change the structure of the tables in the underlying database, as it is used by a number of other applications over which I have no control.

I have several different types of principal-dependent one-to-many relationships defined, where each of the Dependents each needs to have a Principal, and the Principal has many optional Dependents.

As an example, the classes in question all look something like this:

[Table("PrincipalTable")]
public class Principal : BaseClass {
    
    public int TableId { get; set; } // this is the identity column

    [Key]
    public string PrincipalId { get; set; }

    // more properties

    public virtual ICollection<Dependent> Dependents { get; set; }
}

[Table("DependentTable")]
public class Dependent : BaseClass {

    [Key]
    public int DependentId { get; set; }
    
    [Required]
    public string PrincipalId { get; set; }

    // more properties

    public virtual Principal Principal { get; set; }
}

The foreign key relationship for each of these pairs is defined in Fluent API as follows:

modelBuilder.Entity<Principal>()
    .HasMany(e => e.Dependents)
    .WithRequired(e => e.Principal)
    .HasForeignKey(e => e.PrincipalId)
    .WillCascadeOnDelete(true);

When getting a record of type Principal, it correctly returns its Dependents as well, if there are any. So far, so good.

However, I also need to prevent any Dependent from being added without a valid Principal record. This does not appear to be working correctly.

The add method:

public async Task<bool> Add<T>(T entity, Type entityType) where T : class
{
    try
    {
        var contextSet = context.Set(entityType);
        contextSet.Add(entity);
                
        var result = await context.SaveChangesAsync();

        return result > 0;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

In testing this, I can add a Dependent record with a completely bogus PrincipalId, and no foreign key error is thrown.

The "Type entityType" parameter is necessary because T is not known until runtime, and using the generic "context.Set()" returns a DbSet of type BaseClass, not the desired derived entity type. I've confirmed that "context.Set(entityType)" returns the correct DbSet by stepping through the code.

This appears to happen whether the Principal primary key is a string, an int, a decimal, or a composite key.

Edit (4/4/22):

As an example, here is one of the entities in question:

[Table("hdepben")]
public class SageHrDependentBenefit : SageHrEntityBase
{
    [Key]
    [Column("id_col", Order = 0)]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int id_col { get; set; }

    [Column("d_benecode", Order = 1)]
    [StringLength(10)]
    [Required]
    public string d_benecode { get; set; }

    [Column("d_chadate")]
    public DateTime? d_chadate { get; set; }

    [Column("d_company")]
    [StringLength(3)]
    [Required]
    public string d_company { get; set; }

    [Column("d_coverage")]
    [Required]
    public decimal d_coverage { get; set; }

    [Column("d_depid", Order = 5)]
    [Required]
    public decimal d_depid { get; set; }

    [Column("d_effdate")]
    public DateTime? d_effdate { get; set; }

    [Column("d_empno", Order = 7)]
    [StringLength(9)]
    [Required]
    public string SageId { get; set; }

    [Column("d_expdate")]
    public DateTime? d_expdate { get; set; }

    [Column("d_misc1")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc1 { get; set; }

    [Column("d_misc2")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc2 { get; set; }

    [Column("d_misc3")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc3 { get; set; }

    [Column("d_misc4")]
    [StringLength(10)]
    [Required(AllowEmptyStrings = true)]
    public string d_misc4 { get; set; }

    [Column("d_premium")]
    [Required]
    public decimal d_premium { get; set; }

    [Column("d_comments")]
    [Required(AllowEmptyStrings = true)]
    public string d_comments { get; set; }

    [Column("d_courtord")]
    [StringLength(1)]
    [Required(AllowEmptyStrings = true)]
    public string d_courtord { get; set; }

    [Column("d_covfrom")]
    public DateTime? d_covfrom { get; set; }

    [Column("d_covto")]
    public DateTime? d_covto { get; set; }

    [Column("d_ordnumb")]
    [StringLength(25)]
    [Required(AllowEmptyStrings = true)]
    public string d_ordnumb { get; set; }

    [Column("d_crtnotes")]
    [Required(AllowEmptyStrings = true)]
    public string d_crtnotes { get; set; }

    [Column("lckuser")]
    [StringLength(4)]
    public string lckuser { get; set; }

    [Column("lckstat")]
    [StringLength(1)]
    public string lckstat { get; set; }

    [Column("lckdate")]
    public DateTime? lckdate { get; set; }

    [Column("lcktime")]
    [StringLength(8)]
    public string lcktime { get; set; }

    public virtual SageHrDependent Dependent { get; set; }

    public virtual SageHrBenefits Benefit { get; set; }
}

Here is the foreign key mapping in Fluent API:

modelBuilder.Entity<SageHrDependent>()
    .HasMany(e => e.Benefits)
    .WithRequired(e => e.Dependent)
    .HasForeignKey(e => e.d_depid)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<SageHrBenefits>()
    .HasMany(e => e.DependentBenefits)
    .WithRequired(e => e.Benefit)
    .HasForeignKey(e => new { e.d_benecode, e.SageId })
    .WillCascadeOnDelete(true);

This DependentBenefit entity is foreign-key-mapped both to a Dependent entity and a Benefit entity.

The test in question involves posting a dto to a Web API endpoint that wraps all interaction with this database. Once posted, that dto gets mapped to the entity as follows:

public static SageHrDependentBenefit MapToSage(this DependentBenefitDTO dto)
{
    return dto == null ? null : new SageHrDependentBenefit()
    {
        d_benecode = dto.BenefitCode ?? "",
        d_chadate = dto.ChangeDate,
        d_comments = dto.Comments ?? "",
        d_company = dto.Company ?? "",
        d_courtord = dto.d_courtord ?? "",
        d_coverage = dto.CoverageAmount,
        d_covfrom = dto.CoveredFrom,
        d_covto = dto.CoveredTo,
        d_crtnotes = dto.crtnotes ?? "",
        d_depid = dto.DependentId,
        d_effdate = dto.EffectiveDate,
        d_expdate = dto.ExpirationDate,
        id_col = dto.TableId,
        lckdate = dto.lckdate,
        lckstat = dto.lckstat,
        lcktime = dto.lcktime,
        lckuser = dto.lckuser,
        d_misc1 = dto.Misc1 ?? "",
        d_misc2 = dto.Misc2 ?? "",
        d_misc3 = dto.Misc3 ?? "",
        d_misc4 = dto.Misc4 ?? "",
        d_ordnumb = dto.OrderNumber ?? "",
        d_premium = dto.DependentPremium,
        SageId = dto.EmployeeId ?? ""
   };
}

And the bad data being posted to the webservice (that should be throwing the foreign key error):

public static DependentBenefitDTO GetBadNoBeneDummyDependentBenefit(int dependentId)
{
    return new DependentBenefitDTO()
    {
        BenefitCode = "BADDATA",
        ChangeDate = new DateTime(2022, 01, 03),
        Company = "TEC",
        DependentId = dependentId,
        EffectiveDate = new DateTime(2021, 08, 01),
        ExpirationDate = new DateTime(2099, 12, 31),
        EmployeeId = GetDummyEmployeeId()
    };
}

public static DependentBenefitDTO GetBadNoDepDummyDependentBenefit()
{
    return new DependentBenefitDTO()
    {
        BenefitCode = "INH3000",
        ChangeDate = new DateTime(2022, 01, 03),
        Company = "TEC",
        DependentId = 10000,
        EffectiveDate = new DateTime(2021, 08, 01),
        ExpirationDate = new DateTime(2099, 12, 31),
        EmployeeId = "123456"
    };
}

There is no Benefit entity with a benefit code of "BADDATA", and no Dependent entity with DependentId of 10000. However, neither the data with no Benefit parent entity, nor the data with no Dependent parent entity throws a foreign key error.

As mentioned, when getting data from this same db/context, all the expected entities are correctly associated and returned.

Stepping through the code, I've confirmed that the mapping is correctly assigning the values as well.

End edits 4/4/22

Why is no error being thrown on insert?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文