如何在EF Core中添加具有其子记录的父记录

9

下面的示例1曾经可用,但在EF Core中不起作用。 问题: 如何使下面的示例2可用?

示例1

child c1 = new child();
child c2 = new child();

parent p=new parent();
p.child.Add(c1);
p.child.Add(c2);

using (var db = new DbContext())
{
   db.parent.Add(p);
   db.SaveChanges();
}

实体父级P有子级C1和C2,它们之间是一对一的关系。试图插入父级及其子级记录。但在以下代码中,VS2017编辑器的intellisense无法识别.childcust.child.Add(c1);这一行。也许,EF Core 有更好的方法来插入父子级记录。我正在使用ASP.NET MVC Core 1.1.1和EF Core 1.1.1。

例子2:

...
Parent p = new Parent { Name = "some name" , FY = SelectedYear, ... };

Child c1 = new Child { ItemName = "Abc"};
Child c2 = new Child { ItemName = "Rst"};

p.child.Add(c1);
p..child.Add(c2);

_context.Add(p);
_context.SaveChanges();

更新:

根据@GlennSills的请求,以下是一个知名的博客数据库示例(取自ASP.NET教程):

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

在下面的Create(...)方法中,在blog.Posts.Add(c);行处,我收到了错误:Object reference not set to an instance of an object.
public class BlogsController : Controller
{
    private readonly BloggingContext _context;

    public BlogsController(BloggingContext context)
    {
        _context = context;    
    }
....
....
// POST: Blogs/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("BlogId,Url")] Blog blog)
{
    if (ModelState.IsValid)
    {
        Post c = new Post();
        blog.Posts.Add(c);

        _context.Add(blog);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(blog);
}
}

你尝试过在 EF Core 中使用事务吗? - H. Herzl
@H.Herzl 没有,那是什么样的? - nam
你正在使用身份验证吗? - H. Herzl
你需要提供两个实体 Parent 和 Child 的代码,才能正确回答这个问题。如果你已经正确地设置了这两个类之间的关系,那么示例1就可以正常工作。 - GlennSills
你应该将 @GlennSills 的答案标记为被采纳的答案。我知道它解决了我的问题。 - Tod Birdsall
显示剩余3条评论
2个回答

12

将子对象与父对象一起保存绝对可行。关键在于您需要以这样的方式建模实体,以便EF知道所有关系。以下是一个非常简单的应用程序示例,您可以查看它。 Github上的简单应用

我的父对象如下:

using System.Collections.Generic;

namespace EFParentChild
{
    public class Parent
    {
        public int ParentId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public List<Child> Children { get; set; } = new List<Child>();
    }
}

这是子元素

namespace EFParentChild
{
    public class Child
    {
        public int Id { get; set; }
        public int ParentId { get; set; }
        public Parent Parent { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

这里是上下文:

使用 Microsoft.EntityFrameworkCore;

namespace EFParentChild
{
  class DbParentChild : DbContext
  {
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=ParentChildDb;Trusted_Connection=True;");
    }

  }
}

这里是保存数据的程序

static void Main(string[] args)
    {
        using (var dbContext = new DbParentChild())
        {
            var parent = new Parent
            {
                FirstName = "Joe",
                LastName = "Smith"
            };

            parent.Children.Add(
                new Child
                {
                    FirstName = "LittleJoe",
                    LastName = "Smith"
                });
            parent.Children.Add(
                new Child
                {
                    FirstName = "Anne",
                    LastName = "Smith"
                });

            dbContext.Add(parent);
            dbContext.SaveChanges();
        }
    }
关键在于我使用约定告诉EF存在父子关系。注意两个实体之间使用的id名称是匹配的。子元素有一个与其父元素中的ParentId匹配的ParentId。还要注意子元素中的外键约束。
我使用实体框架工具创建了表格。您可以自己采用这种方法,或者手动创建它们。父DDL如下:
CREATE TABLE [dbo].[Parents] (
[ParentId]  INT            IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (MAX) NULL,
[LastName]  NVARCHAR (MAX) NULL,
CONSTRAINT [PK_Parents] PRIMARY KEY CLUSTERED ([ParentId] ASC)
);

子DDL为:

CREATE TABLE [dbo].[Children] (
[Id]        INT            IDENTITY (1, 1) NOT NULL,
[FirstName] NVARCHAR (MAX) NULL,
[LastName]  NVARCHAR (MAX) NULL,
[ParentId]  INT            NOT NULL,
CONSTRAINT [PK_Children] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Children_Parents_ParentId] FOREIGN KEY ([ParentId]) 
REFERENCES [dbo].[Parents] ([ParentId]) ON DELETE CASCADE
);


GO
CREATE NONCLUSTERED INDEX [IX_Children_ParentId]
  ON [dbo].[Children]([ParentId] ASC);

这是否可以在没有集群和约束的情况下实现?我们可以在模型中使用属性来创建一个ID,并通过它进行引用,而不是在SQL中提供外键,就像[Key]表示主键一样。这种做法可行吗? - Md Aslam

1
我假设您的父实体具有自动生成的ID,在这种情况下,您需要将父实体添加到DbSet并保存更改,以便EF Core设置属性的生成值。
尝试此代码,您将保存信息:
// Create instance for parent class
var p = new Parent { Name = "some name", FY = SelectedYear, ... };

// Add entity instance to db context
_context.Parent.Add(p);

// Save changes, this action sets the generated id value to entity's property
_context.SaveChanges();

// Create instances for child entities
var c1 = new Child { ItemName = "Abc", ParentID = p.ParentID };
var c2 = new Child { ItemName = "Rst", ParentID = p.ParentID };

// Add child to db context
_context.Child.Add(c1);
_context.Child.Add(c2);

// Save changes
_context.SaveChanges();

似乎保存失败是因为每个子实例都没有ParentID属性的值,而该列不允许为空值。
如果这有用,请告诉我。

父表和子表都有PK作为标识。而且子表中的FK是可空的。在这种情况下,您建议的解决方案仍然可以吗? - nam
是的,因为在每次保存更改调用时生成标识值。 - H. Herzl
我在 _context.Parents.Add(p); 这行代码处遇到错误 MyProjContext 不包含名称为 Parents 的定义。MyProjContext 只是一个普通的类,代码如下:`public class MyProjContext : DbContext { public MyProjContext(DbContextOptions<MyProjContext> options) : base(options) {} public DbSet Parent{ get; set; } public DbSet Child{ get; set; }}` - nam
在这种情况下,您必须将dbset属性的名称从Parents更改为Parent。 - H. Herzl

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