【问题标题】:EF6 generating empty child entity for nullable foreign keyEF6 为可为空的外键生成空子实体
【发布时间】:2016-11-06 15:39:49
【问题描述】:

过去几天我一直在努力解决这个问题。我有一个 Web 应用程序,它与 WebAPI API 通信以检索和更新由“实体框架反向 POCO CodeFirst 生成器”Visual Studio 扩展创建和维护的实体。他们似乎都生成了实体和 DbContext 就好了。但问题是:Web 客户端对返回“Patient”实体的 API 方法进行 ajax 调用。这个患者实体有几个可以为空的外键,即 LanguageId 和 PharmacyId。现在,当存储库检索到 Patient 时,它在服务器 上看起来很好。 LanguageId 和 PharmacyId 正如预期的那样为空。 Patient 的 Navigation 属性,即 luLanguage 和 Pharmacy 也正确设置为 null。客户端收到的json对象此时看起来也很好。外键 ID 和导航属性均为空。客户端对患者对象进行一些可选的编辑并将对象发送回服务器。就在 POST ajax 调用之前,Patient json 对象仍然像预期的那样看起来很好 。外键和导航属性为空。但是,一旦 WebAPI 方法接收到该对象,奇怪的是,有空的语言和药房导航属性。外键对应的主键是INT,它们初始化为0。这使得DbContext在尝试更新它们时发疯,因为Language的外键和Language导航对象中的主键不匹配。一个为空,另一个为 0(零)。我尝试了很多不同的测试,但无论我如何削减它,我都会回到同样的问题。

这是实体模型构建器的配置代码。

public PatientConfiguration(string schema)
    {
        ToTable("Patient", schema);
        HasKey(x => x.Id);

        Property(x => x.Id).HasColumnName(@"ID").IsRequired().HasColumnType("bigint").HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
        Property(x => x.LastName).HasColumnName(@"LastName").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(50);
        Property(x => x.FirstName).HasColumnName(@"FirstName").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(50);
        Property(x => x.MiddleName).HasColumnName(@"MiddleName").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(30);
        Property(x => x.Gender).HasColumnName(@"Gender").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(18);
        Property(x => x.Dob).HasColumnName(@"DOB").IsRequired().HasColumnType("datetime");
        Property(x => x.SocialSecurity).HasColumnName(@"SocialSecurity").IsOptional().HasColumnType("varbinary");
        Property(x => x.LastFourSsn).HasColumnName(@"LastFourSSN").IsOptional().HasColumnType("int");
        Property(x => x.PharmacyId).HasColumnName(@"PharmacyID").IsOptional().HasColumnType("bigint");
        Property(x => x.InUseById).HasColumnName(@"InUseByID").IsOptional().HasColumnType("bigint");
        Property(x => x.LanguageId).HasColumnName(@"LanguageID").IsOptional().HasColumnType("int");
        Property(x => x.Status).HasColumnName(@"Status").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(10);
        Property(x => x.IsInactive).HasColumnName(@"IsInactive").IsRequired().HasColumnType("bit");
        Property(x => x.CreatedById).HasColumnName(@"CreatedByID").IsRequired().HasColumnType("bigint");
        Property(x => x.CreatedDate).HasColumnName(@"CreatedDate").IsRequired().HasColumnType("datetime");
        Property(x => x.UpdatedById).HasColumnName(@"UpdatedByID").IsRequired().HasColumnType("bigint");
        Property(x => x.UpdatedDate).HasColumnName(@"UpdatedDate").IsRequired().HasColumnType("datetime");
        Property(x => x.MigratedBy).HasColumnName(@"MigratedBy").IsOptional().IsUnicode(false).HasColumnType("varchar").HasMaxLength(10);
        Property(x => x.TimeStamped).HasColumnName(@"TimeStamped").IsRequired().IsFixedLength().HasColumnType("timestamp").HasMaxLength(8).IsRowVersion().HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed);

        // Foreign keys
        HasOptional(a => a.LuLanguage).WithMany(b => b.Patients).HasForeignKey(c => c.LanguageId).WillCascadeOnDelete(false); // FK_Patient_luLanguage
        HasOptional(a => a.Pharmacy).WithMany(b => b.Patients).HasForeignKey(c => c.PharmacyId).WillCascadeOnDelete(false); // FK_Pharmacy_Patient
        InitializePartial();
    }

这是生成的患者实体

public partial class Patient : EntityBase
{
    //[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; } // ID (Primary key)
    public string LastName { get; set; } // LastName (length: 50)
    public string FirstName { get; set; } // FirstName (length: 50)
    public string MiddleName { get; set; } // MiddleName (length: 30)
    public string Gender { get; set; } // Gender (length: 18)
    public System.DateTime Dob { get; set; } // DOB
    public byte[] SocialSecurity { get; set; } // SocialSecurity
    public int? LastFourSsn { get; set; } // LastFourSSN
    public long? PharmacyId { get; set; } // PharmacyID
    public long? InUseById { get; set; } // InUseByID
    public int? LanguageId { get; set; } // LanguageID
    public string Status { get; set; } // Status (length: 10)
    public bool IsInactive { get; set; } // IsInactive
    public long CreatedById { get; set; } // CreatedByID
    public System.DateTime CreatedDate { get; set; } // CreatedDate
    public long UpdatedById { get; set; } // UpdatedByID
    public System.DateTime UpdatedDate { get; set; } // UpdatedDate
    public string MigratedBy { get; set; } // MigratedBy (length: 10)
    public byte[] TimeStamped { get; private set; } // TimeStamped (length: 8)

    // Reverse navigation
    public virtual System.Collections.Generic.ICollection<AuditPatientChange> AuditPatientChanges { get; set; } // auditPatientChange.R_88
    public virtual System.Collections.Generic.ICollection<PatientAddress> PatientAddresses { get; set; } // PatientAddress.FK_PatientAddress_Patient
    public virtual System.Collections.Generic.ICollection<PatientCallTracking> PatientCallTrackings { get; set; } // PatientCallTracking.R_74
    public virtual System.Collections.Generic.ICollection<PatientContact> PatientContacts { get; set; } // PatientContact.FK_PatientContact_Patient
    public virtual System.Collections.Generic.ICollection<PatientCreditCard> PatientCreditCards { get; set; } // PatientCreditCard.R_59
    public virtual System.Collections.Generic.ICollection<PatientDiseaseState> PatientDiseaseStates { get; set; } // PatientDiseaseState.R_65
    public virtual System.Collections.Generic.ICollection<PatientIcd10> PatientIcd10 { get; set; } // PatientICD10.R_63
    public virtual System.Collections.Generic.ICollection<PatientMedicalHistory> PatientMedicalHistories { get; set; } // PatientMedicalHistory.R_60
    public virtual System.Collections.Generic.ICollection<PatientNoteAndComment> PatientNoteAndComments { get; set; } // PatientNoteAndComment.R_73
    public virtual System.Collections.Generic.ICollection<PatientPayment> PatientPayments { get; set; } // PatientPayment.R_56
    public virtual System.Collections.Generic.ICollection<PatientPlanPolicy> PatientPlanPolicies { get; set; } // PatientPlanPolicy.R_90
    public virtual System.Collections.Generic.ICollection<PatientReferral> PatientReferrals { get; set; } // PatientReferral.FK_PatientReferral_Patient
    public virtual System.Collections.Generic.ICollection<PatientShareOfCost> PatientShareOfCosts { get; set; } // PatientShareOfCost.R_62
    public virtual System.Collections.Generic.ICollection<Rx> Rxes { get; set; } // Rx.FK_Rx_Patient

    // Foreign keys
    public virtual LuLanguage LuLanguage { get; set; } // FK_Patient_luLanguage
    public virtual Pharmacy Pharmacy { get; set; } // FK_Pharmacy_Patient

    public Patient()
    {
        IsInactive = false;
        CreatedById = 0;
        UpdatedById = 0;
        AuditPatientChanges = new System.Collections.Generic.List<AuditPatientChange>();
        PatientAddresses = new System.Collections.Generic.List<PatientAddress>();
        PatientCallTrackings = new System.Collections.Generic.List<PatientCallTracking>();
        PatientContacts = new System.Collections.Generic.List<PatientContact>();
        PatientCreditCards = new System.Collections.Generic.List<PatientCreditCard>();
        PatientDiseaseStates = new System.Collections.Generic.List<PatientDiseaseState>();
        PatientIcd10 = new System.Collections.Generic.List<PatientIcd10>();
        PatientMedicalHistories = new System.Collections.Generic.List<PatientMedicalHistory>();
        PatientNoteAndComments = new System.Collections.Generic.List<PatientNoteAndComment>();
        PatientPayments = new System.Collections.Generic.List<PatientPayment>();
        PatientPlanPolicies = new System.Collections.Generic.List<PatientPlanPolicy>();
        PatientReferrals = new System.Collections.Generic.List<PatientReferral>();
        PatientShareOfCosts = new System.Collections.Generic.List<PatientShareOfCost>();
        Rxes = new System.Collections.Generic.List<Rx>();
        InitializePartial();
    }

    partial void InitializePartial();
}

这里是测试这个问题的客户端javascript代码:

function testx1(){

var patient;

$.ajax({
    type: 'GET',
    url: "/api/patient/patient/61143",
    success: function (result) {
        patient = result;
        testx2(patient);
    },
    error: function (xhr, options, error) {
        debugger;
        alert(error);
    }
});
}

function testx2(patient) {
    patient.LastFourSsn = 5555;
    patient.ObjectStateEnum = 2;
    var x = "adsf";
    $.ajax({
        type: 'POST',
        url: "/api/patient/patient",
        data: patient,
        dataType: "json",
        success: function (result) {
        },
        error: function (xhr, options, error) {
        }
     });
}

我的 WebAPI 代码:

        [Route("patient/{id}")]
    [HttpGet]
    public async Task<IHttpActionResult> GetPatientByIdAsync( long id )
    {
        try
        {
            var patient = await patientService.GetPatientByIdAsync( id );
            if ( patient == null )
                return NotFound();
            return Ok( patient );
        }
        catch ( Exception ex )
        {
            return InternalServerError();
        }
    }

    [Route("patient")]
    [HttpPost]
    public async Task<IHttpActionResult> SavePatient( Patient patient )
    {
        try
        {
            //if ( patient.LanguageId == null && patient.LuLanguage?.Id == 0 )
            //  patient.LuLanguage = null;
            //if ( patient.PharmacyId == null && patient.Pharmacy?.Id == 0 )
            //  patient.Pharmacy = null;
            IOperationStatus result = await patientService.UpdateAndSavePatientAsync(patient);
            if ( !result.Success )
                return InternalServerError( new Exception( result.Message ) );
            else
                return Ok();
        }
        catch ( Exception ex)
        {
            return InternalServerError(ex);
        }
    }
}

这是我的 Json 序列化程序设置:

    public class BrowserJsonFormatter : JsonMediaTypeFormatter
{
    public BrowserJsonFormatter()
    {
        this.SupportedMediaTypes.Add( new MediaTypeHeaderValue( "text/html" ) );
        this.SerializerSettings.Formatting = Formatting.Indented;
    }

    public override void SetDefaultContentHeaders( Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType )
    {
        base.SetDefaultContentHeaders( type, headers, mediaType );
        headers.ContentType = new MediaTypeHeaderValue( "application/json" );
    }
}

【问题讨论】:

  • 能否也包括您的实际 webapi 方法代码?
  • @Wiktor :我编辑了我的帖子以包含 WebAPI 方法代码。谢谢。
  • 那么当你调用SavePatient时,绑定的参数patient已经有错误的值了(0而不是null)?
  • 是的,没错。我在调用 post 方法之前检查了客户端上的患者对象,它看起来不错。 luLanguage 为空。但是当调用到达服务器 API 时,luLanguage 不是 null,而是一个 id 为 0 的空对象。奇怪,我知道。看在上帝的份上,我不知道发生了什么事。
  • 您能否使用 http 调试器嗅探客户端和服务器之间的实际流量,以 100% 确定实体通过网络传输两个空值?在这里看不出任何明显的东西。实体或映射中的 InitializePartial 中是否存在某些内容?

标签: c# json asp.net-web-api entity-framework-6


【解决方案1】:

我相信您的问题与您如何序列化对象有关。

要确认这一点,请手动将 Language 和 LanguageLu 属性添加到您的 POST。

function testx2(patient) {
patient.LastFourSsn = 5555;
patient.ObjectStateEnum = 2;
patient.LuLanguage = null;
patient.Language = null;
var x = "adsf";
$.ajax({
    type: 'POST',
    url: "/api/patient/patient",
    data: patient,
    dataType: "json",
    success: function (result) {
    },
    error: function (xhr, options, error) {
    }
 });

}

如果一切正常,那么我们可以确定患者对象的序列化存在问题,您应该发布您正在使用的 JSON 序列化程序的代码。

否则我会进一步研究流畅的 API 语法。

【讨论】:

  • 谢谢。我编辑了我的帖子以显示我的序列化设置。至于您的回答,当 testx2(patient) javascript 函数接收到时,patient.LuLanguage 已经为空。所以,我假设我不需要手动再次设置它。即使这可行,我也不想走那条路,因为我有 100 多个实体被客户端方法使用,例如这样。此外,它们中的许多都具有深度嵌套的导航属性。在客户端上手动设置它们会适得其反。所以,我需要找到更好的解决方案/方法。
  • @BabuMannavalappil 这仅用于诊断目的。看起来 Json (de)serializer 初始化了默认对象。
【解决方案2】:

我必须对我的问题进行一些更改才能解决它。首先,我必须对我的 json 对象执行 JSON.stringify,该对象将被 POST 回服务器进行更新,并在 ajax 帖子中特别提到“contentType:'application/json'”。其次,我不得不使用内置于 ADO.NET 实体数据模型扩展的 VS 来生成我的 DbContext,而不是使用实体框架反向 POCO 生成器。随着这两个变化,事情开始起作用了。我敢肯定,如果我深入研究这两种工具生成的 dbContext 代码,我可能会发现我的时间戳(行版本)列没有被我的 WebAPI 方法正确接收的原因。但由于时间紧迫,我不得不将调查推迟一天。它现在有效,这就是所有问题,至少现在是这样。感谢大家的帮助。

编辑:以防其他人遇到同样的问题,这是我在尝试解决这个问题两天后发现的。事实证明,“实体框架反向 POCO 生成器”使用 {get; 创建 rowversion 属性。 private set} 确实如此。但是当 WebAPI 方法接收到 POST 请求时,该过程可能无法从 json 生成实体类并将时间戳列设置为该值。因此,我编辑了 .tt 文件,让时间戳列具有公共获取和设置 {get;set;}。这解决了我所有的问题。我仍然希望行版本(时间戳)列的私有设置器并以某种方式设置值。但由于时间紧迫,我只能满足于这个解决方案。

【讨论】:

    猜你喜欢
    • 2013-08-14
    • 2019-10-16
    • 2011-09-06
    • 2020-12-07
    • 1970-01-01
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多