【问题标题】:EF6 EntityValidationErrors when adding an entity with FK to an existing Entity (using only the id)将具有 FK 的实体添加到现有实体时出现 EF6 EntityValidationErrors(仅使用 id)
【发布时间】:2014-05-05 19:10:25
【问题描述】:

我有以下场景。

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string AppId { get; set; }
    public string Token { get; set; }
    public ICollection<Document> Documents { get; set; }
}


public class Document
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte[] PdfBinary { get; set; }
    public string PdfFileName { get; set; }
    public int SignersCount { get; set; }
    public int CompanyId { get; set; }
    public Company Company { get; set; }
    public int Pages { get; set; }

    public ICollection<TagDefinition> Tags { get; set; }

    public Document()
    {
        Company = new Entities.Company();
        Tags = new List<TagDefinition>();
    }
}

现在,这是两者的映射器或配置文件

public class CompanyMapper: EntityTypeConfiguration<Company>
{
    public CompanyMapper()
    {
        this.ToTable("Companies");

        this.HasKey(c => c.Id);
        this.Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(c => c.Id).IsRequired();

        this.Property(c => c.Name).IsRequired();
        this.Property(c => c.AppId).IsRequired();
        this.Property(c => c.Token).IsRequired();
    }
}

 public class DocumentMapper: EntityTypeConfiguration<Document>
{
    public DocumentMapper()
    {
        this.ToTable("Documents");

        this.HasKey(c => c.Id);
        this.Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(c => c.Id).IsRequired();

        this.Property(c => c.Name).IsRequired();
        this.Property(c => c.Name).HasMaxLength(255);

        this.Property(c => c.Pages).IsOptional();

        this.Property(c => c.SignersCount).IsRequired();

        this.Property(c => c.PdfBinary).IsRequired();
        this.Property(c => c.PdfBinary).IsMaxLength();

        this.Property(c => c.PdfFileName).IsRequired();

        HasRequired(c => c.Company).WithMany(c => c.Documents).HasForeignKey(c => c.CompanyId);
    }
}

这就是问题所在。 (参见本说明下方的代码)

我想创建一个新文档...所以,当我尝试将一个新文档添加到我的数据库并仅设置 CompanyId(不是整个 Company 实例)时,我收到以下错误...

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

当我查看 GetValidationErrors() 集合时,它基本上告诉我公司的字段是空的(它们在数据库中被标记为必需)。因此,似乎正在尝试为 Company id=1 创建一条新记录……这很奇怪,因为它已经存在。我对这个 EF6 的工作方式感到非常困惑。我的理解是,通过在我的 Document 对象中设置 ComapanyId 属性,然后使用配置文件对其进行映射,足以使 INSERT 按预期工作。现在,如果我传递给整个公司实体的文档,那么它会按预期工作。

CompanyId 信息不足以让 EF 知道它属于 Companies 表中 id = 1 的记录吗?

var doc = new Document(){ Name = "Name",
        PdfFileName = "Filename",
        CompanyId = 1};
        _repo.Create(doc);

这里是存储库...

public abstract class WriteRepository<TContext> : IWriteRepository where TContext : DbContext, new()
{
    private readonly TContext _context;

    protected TContext Context { get { return _context; } }

    protected WriteRepository()
    {
        _context = new TContext();
    }

    public void Dispose()
    {
        _context.Dispose();
    }

    ...

    public TItem Create<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
    {
        return PerformAction(item, EntityState.Added, saveImmediately);
    }

    protected virtual TItem PerformAction<TItem>(TItem item, EntityState entityState, bool saveImmediately = true) where TItem : class, new()
    {
        _context.Entry(item).State = entityState;
        if (saveImmediately)
        {
            try
            {
                _context.SaveChanges();
            }
            catch (Exception ex)
            {
                var x = _context.GetValidationErrors();
            }
        }
        return item;
    }

在我看来很奇怪,如果我有一个 FK 的有效 ID,我仍然必须将整个 Company 实例传递给文档实例。我试图找到有关它的文档。我发现与我类似的例子似乎对他们有用,但对我来说不是:(

提前谢谢你...

【问题讨论】:

    标签: asp.net sql entity-framework repository-pattern


    【解决方案1】:

    首先,您不必设置自己的 EntityState。如果您正确建模您的关系,EF 应该为您跟踪。

    您最好将公司视为一条聚合路线。然后让它处理建立关系。这样的事情将是一个很好的起点:

    var company = companySet.FirstOrDefault(x => x.Id == 1);
    
    var newDoc = new Document()
    {                
        Name = "foo doc",
        Pages = 1,
        SignersCount = 0,
        PdfBinary = new byte[]{},
        PdfFileName = "foo.pdf"
    };
    
    company.AddDocument(newDoc);
    

    然后在 Company 类中:

    public void AddDocument(Document document)
    {
        document.Company = this;
        Documents.Add(document);
    }
    

    这里的关键是您需要将文档添加到 Companies 文档集合中才能正确保存关系数据。

    然后你只需要在你的 DbContext 上调用 SaveChanges。你如何做到这一点取决于你。您可能需要稍微尝试一下您的存储库设计。

    希望这会有所帮助。

    【讨论】:

    • 感谢您的意见。我不同意你。我非常了解 DDD。仅仅因为一个实体感觉“自然”作为另一个实体的父级,加载整个 Graph 来保存子实体不应该是足够的理由。文档本身就是一个聚合根。仅仅因为 City 对象“感觉”像 State 对象的子对象等等直到 Universe 对象,并不意味着您需要加载 Universe 来保存 City。即使在 AR 之间,仅通过 ID 相互引用也是一种很好的做法。但无论如何,我会调查 EntityState。我是 EF 的新手……NHibernate 是我选择的 ORM。
    • 我认为这取决于您如何与系统中的实体进行交互。我确实假设文档是在公司环境中创建的,这是我的错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-29
    相关资源
    最近更新 更多