发生了错误,无法保存不公开外键属性的实体关系。

54

我有一个简单的 Entity Framework (EF) v4.1 code first 的代码:

PasmISOContext db = new PasmISOContext();
var user = new User();
user.CreationDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
user.LastLoginDate = DateTime.Now;
db.Users.Add(user);

db.SaveChanges();
user.Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") };
db.SaveChanges();


db.Users.Add(new User() { Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") } });
db.SaveChanges();

问题在于我遇到了一个错误

在保存不公开其关系的外键属性的实体时发生了错误。EntityEntries属性将返回null,因为无法将单个实体标识为异常来源。通过在实体类型中公开外键属性,可以更轻松地处理保存时的异常。有关详细信息,请参见InnerException。

在:

db.Users.Add(new User() { Avatar = new Avatar() { Link = new Uri("http://myUrl/%2E%2E/%2E%2E") } });
db.SaveChanges();
  

我不明白为什么相似的操作可以正常工作。是我的模型出了问题,还是ef-code-first有问题?

public class Avatar
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string LinkInString { get; set; }

    [NotMapped]
    public Uri Link
    {
        get { return new Uri(LinkInString); }
        set { LinkInString = value.AbsoluteUri; }
    }
}

public class User
{
    [Key]
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public Avatar Avatar { get; set; }
    public virtual ICollection<Question> Questions { get; set; }
    public virtual ICollection<Achievement> Achievements { get; set; }

    public DateTime CreationDate { get; set; }
    public DateTime LastLoginDate { get; set; }
    public DateTime LastActivityDate { get; set; }
}

1
我无法复现这个错误。你能检查一下第一个代码片段吗?那是否确切地就是你所做的?(至少缺失了一个 db.Users.Add(user),否则这两个 SaveChanges 没有任何意义。)你有任何额外的Fluent API映射吗? - Slauma
谢谢Slauma。我已经编辑了我的代码。我没有任何流畅的API。第一次和第二次SaveChanges会向Avatars和Users表添加行。我不知道这是否重要,但在表中我有Avatar_Id列。 - user278618
8
User表中的Avatar_IdAvatar导航属性的外键列,这没问题。你是手动创建数据库表还是让EF创建数据库?目前我不知道你为什么会出现这个错误。你可以尝试按照异常中给出的建议,在User类中添加一个外键属性:public int? AvatarId { get; set; },看看会发生什么。也许至少异常会揭示更多关于问题所在的细节。 - Slauma
我让EF创建数据库。就像你说的那样,我添加了这个属性,现在它运行良好 - 现在我有AvatarId而不是Avatar_Id,并且它保持正确的avatarid。这很奇怪,但感谢你的帮助。 - user278618
你可以将其发布为答案,说明FK属性解决了问题,然后接受你的答案,以结束这个问题。我不明白为什么它解决了你的问题,对我来说,即使没有FK属性,它也能正常工作。 - Slauma
16个回答

182

如果你在所有键都正确定义的情况下仍然遇到此错误,请检查您的实体并确保不要将日期时间字段留空。


11
如果您查看内部异常,会发现一种类型为"System.Data.SqlClient.SqlException"的异常,其消息为:"将datetime2数据类型转换为datetime数据类型时导致超出范围的值。" 这是由于插入了一个null DateTime值到一个不允许null值的列中引起的。请参阅https://dev59.com/YnM_5IYBdhLWcg3wjj0p。 - Ryan Kyle
我的内部异常与datetime2转换无关,但我将datetime设置为null。数据库列允许为空,所以我认为这不是问题,但事实证明它确实是问题。即使您认为这不是您的问题,请尝试此解决方法! - vaindil
你好 @SarangK,你能告诉我如何解决这个问题吗? - Med Elgarnaoui
谢谢,它帮了我很多,也节省了我很多时间! - hotfusion
@MohamedElgarnaoui 我检查了所有的DateTime字段,在'context.SaveChanges();'之前都被初始化了。 - SarangK
这适用于任何数据类型。我有一个项目,通过多个方法传递数据,但在一个DTO中未设置字符串字段。C#不关心,但数据库字段是非空的,所以SQL Server会报错。 - Wildcat Matt

16

这个错误信息可能由任何原因引起。'InnerException'属性(或其InnerException,或该属性的InnerException等) 包含了实际的问题根本原因。

当然,知道问题发生在哪里会很有用——哪些工作单元中的对象导致了问题?异常消息通常会告诉您在 'EntityEntries' 属性中,但在这种情况下,由于某种原因无法执行该操作。这种 'EntityEntries' 属性为空的诊断复杂性显然是因为某些实体“不公开其关系的外键属性”。

即使 OP 由于未能初始化第二个 User 实例的 DateTimes 而出现错误,他们仍然会遇到诊断复杂性——'EntityEntries' 为空,并且存在一个令人困惑的顶级消息......因为其中一个实体没有“公开外键属性”。要解决此问题,Avatar 应该定义一个 public virtual ICollection<User> Users { get; set; } 属性。


6
通过添加一个FK属性,该问题得到了解决。

1
我偶然发现了这个答案。在数据库初始化(dropcreatedb)期间,我遇到了这个错误。我已经有一个外键,但将其设置为可空解决了我的问题。 - Ben Felda
我不知道为什么楼主没有将这个答案标记为被接受的答案,因为它明显解决了问题。请查看问题下面的评论。 - Gert Arnold

3

在我的情况下,以下情况给了我相同的异常:

假设您有一个代码优先EF模型,其中具有Garage实体的Car实体集合。我需要从车库中删除一辆汽车,因此我最终编写了下面这样的代码:

garageEntity.Cars.Remove(carEntity);

相反,它应该是这个样子:

context.Cars.Remove(carEntity);

1
假设汽车必须有一个车库,是的。否则,您将尝试在汽车对象中传递GarageId的空值。 - Flater

2

在我的情况下,异常是因为EF创建了一个错误的迁移。它忘记在第二个表上设置identity: true。所以进入创建相关表的迁移,并检查是否忘记添加标识。

CreateTable(
    "dbo.LogEmailAddressStats",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            EmailAddress = c.String(),
        })
    .PrimaryKey(t => t.Id);

CreateTable(
    "dbo.LogEmailAddressStatsFails",
    c => new
        {
            Id = c.Int(nullable: false), // EF missed to set identity: true!!
            Timestamp = c.DateTime(nullable: false),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.LogEmailAddressStats", t => t.Id)
    .Index(t => t.Id);

一个 Id 列应该具有自增特性,因此这必须是一个 EF 的 bug。您可以直接使用 SQL 在数据库中手动添加自增特性,但我更喜欢使用 Entity Framework。如果您遇到同样的问题,我看到了两个简单的解决方案:
备选方案 1:回滚错误创建的迁移
update-database -target:{insert the name of the previous migration}

然后在迁移代码中手动添加identity: true,然后再次运行update-database

备选方案2

创建一个新的迁移,添加身份验证。如果模型没有更改并且您运行了update-database

add-migration identity_fix

它将创建一个空的迁移。然后只需要添加这个。
    public partial class identity_fix : DbMigration
    {
        public override void Up()
        {
            AlterColumn("dbo.LogEmailAddressStatsFails", "Id", c => c.Int(nullable: false, identity: true));
        }

        public override void Down()
        {
            AlterColumn("dbo.LogEmailAddressStatsFails", "Id", c => c.Int(nullable: false));
        }
    }

1
这个表重命名让我措手不及。谢谢。 - Tristan Warner-Smith

2

这里是为了帮助其他可能遇到类似问题的人。我也曾经遇到过同样的错误,但原因不同。在我的子对象中,我定义了一个 [Key] 值,这个值对于不同的保存来说是相同的。这是我的一个愚蠢错误,但错误信息并没有立刻指出问题所在。


1

我遇到了同样的问题。在我的情况下,是由于一个空值的日期时间字段引起的。我不得不传递一个值给日期时间字段,然后一切都顺利了。


1

这个问题也可能是由于键声明的反转而引起的。如果您正在使用Fluent配置关系,请确保左右键映射到正确的实体。


0

另一个答案:

我用了这个:

public List<EdiSegment> EdiSegments { get; set; }

用它代替:

public virtual ICollection<EdiSegment> EdiSegments { get; set; }

并且收到了上述的错误信息。


0

我不确定这对你的情况是否有帮助,因为我正在使用Fluent API设置我的表,但是据我所知,无论是使用数据注释(属性)还是Fluent API(配置)设置架构,问题都会出现。

EF(v.6.1.3)似乎存在一个错误,当更新到下一个迁移时,它会忽略某些对架构的更改。在开发阶段,最快的解决方法是从数据库中删除所有表格,并再次运行初始化阶段的迁移。

如果您已经处于生产状态,则我找到的最快解决方案是手动更改数据库中的架构,或者如果您想对更改进行版本控制,则手动操作迁移中的Up()Down()方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接