jk's notes
  • 插入数据

插入数据

现在, 我们已经学习了如何使用 Entity Framework Core 5 展示我们数据库中的数据. 下面来看看如何将数据插入到数据库中. 我会介绍如果将数据插入到主表, 同时使用导航属性将数据插入到子表中.

插入根数据

使用 Entity Framework Core 5 将根数据插入数据库的代码很简单. 通过 DbSet<T>.Add 方法添加新记录. 例如, 使用 EF Core 将一条新的不带有 Address 的 Person 记录插入到数据库中, 你可以使用下面的代码:

using (var context = new AppDbContext()) 
{
    context.Persons.Add(new Person
    {
        FirstName = "Clarke",
        LastName = "Kent",
        CreatedOn = DateTime.Now,
        EmailAddress = "clark@daileybugel.com",
    });
    context.SaveChanges();
}

第一步是创建一个新的 DbContext. 然后我们在 Person 的 DbSet 属性上调用 Add 方法, 并传入一个新的 Person 实例. 最后, 调用 DbContext 上的 SaveChanges 方法将添加的所有记录提交到数据库中.

插入子记录

子记录可以简单的使用导航输入插入. Person 类含有一个导航属性 Addresses. 例如, 添加同一个 Person 记录, 并同时添加一条地址, 你可以使用下面的代码:

using(var context = new AppDbContext())
{
    context.Persons.Add(new Person
    {
        FirstName = "Clarke",
        LastName = "Kent",
        CreatedOn = DateTime.Now,
        EmailAddress = "clark@daileybugel.com",
        Addresses = new List<Address> 
        {
            new Address 
            {
                AddressLine1 = "1234 Fake Street",
                AddressLine2 = "Suite 1",
                City = "Chicago",
                State = "IL",
                ZipCode = "60652",
                Country = "United States"
            }
        }
    });
    context.SaveChanges();
}

你可以添加多个子记录, 如果导航属性与其父记录是一对多的关系, 好比我们的 Addresses 属性一样. 例如, 若要添加两条地址到 Person 中, 你可以使用下面代码:

using (var context = new AppDbContext())
{
    context.Persons.Add(new Person()
    {
        FirstName = "Clarke",
        LastName = "Kent",
        CreatedOn = DateTime.Now,
        EmailAddress = "clark@daileybugel.com",
        Addresses = new List<Address>
        {
            new Address
            {
                AddressLine1 = "1234 Fake Street",
                AddressLine2 = "Suite 1",
                City = "Chicago",
                State = "IL",
                ZipCode = "60652",
                Country = "United States"
            },
            new Address
            {
                AddressLine1 = "555 Waverly Street",
                AddressLine2 = "APT B2",
                City = "Mt. Pleasant",
                State = "MI",
                ZipCode = "48858",
                Country = "USA"
            }
        }
    });
    context.SaveChanges();
}

主键值

主键是记录在数据库中的唯一标识. 大多数情况下它是单列, 但是它也可以是又多个列组合成的联合主键. 你可以指定主键值自动增长, 也可以从程序中设置它们.

使用标识种子主键

Entity Framework Core 5 默认使用标识种子 (Identity Seeded) 来设置主键. 也就是说, 无论何时, 一条新纪录插入到数据库中, 它的主键值会比上一条插入的记录的主键值大. 主键的数据类型可以是任意的整数类型, 例如 short, int 或 long.

Guid 主键

如果 Id 属性的数据类型是 Guid, 那么 Entity Framework Core 5 会命令数据库在插入数据时生成全局唯一标识符作为主键值.

非计算主键

如果希望应用程序显式的设置主键值, 那么可以在主键列上使用 DatabaseGenerated 特性, 例如:

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }

通过设置 DatabaseGeneratedOption.None 值, 就是在告诉 Entity Framework, 应用程序会自己负责主键值.

外键

如果你添加一个子实体到你的父记录中, 例如通过导航属性 Addresses 来添加 Address, 那么 Entity Framework Core 5 会自动的生成子实体的主键, 并适当的设置外键值.

默认值

如果在插入记录是, 你倾向于使用硬编码, 或数据库生成的值, 那么你可以在 DbContext 上下文实例的 OnModelCreating 方法中使用 HasDefaultValue 或 HasDefaultValueSql 方法. 例如, 要将 Person 类中的 CreatedOn 属性的 DateTime 值设置为默认的 getdate(), 我们可以使用下面的代码:

protected override void OnModelCtreating(ModelBuilder modelBuilder) 
{
    modelBuilder.Entity<Person>()
        .Property(x => x.CreatedOn).HasDefaultValueSql("getdate()");
}

类似的, 如果我们希望 Address 实体的 Country 属性使用默认值 "USA", 我们可以在 OnModelCreating 方法中使用下面的代码:

protected override void OnModelCreating(ModelBuilder modelBuilder) 
{
    moduleBuilder.Entity<Address>()
        .Property(x => x.Country).HasDefaultValue("USA");
}

插入记录的集成测试

现在我们已经可以使用 Entity Framework Core 5 将记录插入到数据库中了. 下面让我们来通过一些单元测试来看看这个操作. 在 EFCore5WebApp.DAL.Tests 项目中创建一个类 AddTests.

然后添加下面代码到你的 AddTests 类中

using EFCore5WebApp.Core.Entities;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EFCore5WebApp.DAL.Tests
{
    [TestFixture]
    public class AddTests
    {
        private AppDbContext _context;
        
        [SetUp]
        public void SetUp()
        {
            _context = new AppDbContext(
                new DbContextOptionsBuilder<AppDbContext>()
                .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EfCore5WebApp;Trusted_Connection=True;MultipleActiveResultSets=true")
                .Options
            );
        }
        
        [Test]
        public void InsertPersonWithAddresses()
        {
            var record = new Person()
            {
                FirstName = "Clarke",
                LastName = "Kent",
                EmailAddress = "clark@daileybugel.com",
                Addresses = new List<Address>
                {
                    new Address
                    {
                        AddressLine1 = "1234 Fake Street",
                        AddressLine2 = "Suite 1",
                        City = "Chicage",
                        State = "IL",
                        ZipCode = "60652",
                        Country = "United States"
                    },
                    new Address
                    {
                        AddressLine1 = "555 Waverly Street",
                        AddressLine2 = "APT B2",
                        City = "Mt. Pleasant",
                        State = "MI",
                        ZipCode = "48858",
                        Country = "USA"
                    }
                }
            };
            
            _context.Persons.Add(record);
            _context.SaveChanges();
            
            var addedPerson = _context.Persons
                .Single(
                x => x.FirstName == "Clarke" && x.LastName == "Kent");
            
            Assert.Greater(addedPerson.Id, 0);
            Assert.AreEqual(2, addedPerson.Addresses.Count);
            Assert.AreEqual(record.FirstName, addedPerson.FirstName);
            Assert.AreEqual(record.LastName, addedPerson.LastName);
            Assert.AreEqual(record.EmailAddress, addedPerson.EmailAddress);
            
            for(int i = 0; i < record.Addresses.Count; i++)
            {
                Assert.AreEqual(record.Addresses[i].AddressLin1,
                               addedPerson.Addresses[i].AddressLine1);
                Assert.AreEqual(record.Addresses[i].AddressLin2,
                               addedPerson.Addresses[i].AddressLine2);
                Assert.AreEqual(record.Addresses[i].City,
                               addedPerson.Addresses[i].City);
                Assert.AreEqual(record.Addresses[i].State,
                               addedPerson.Addresses[i].State);
                Assert.AreEqual(record.Addresses[i].ZipCode,
                               addedPerson.Addresses[i].ZipCode);
                Assert.AreEqual(record.Addresses[i].Country,
                               addedPerson.Addresses[i].Country);
            }
        }
        
        [TearDown]
        public void TearDown()
        {
            var person = _context.Persons
                .Single(x => x.FirstName = "Clarke" && x.LastName = "kent");
            _context.Persons.Remove(person);
            _context.SaveChanges();
        }
    }
}

我们所做的第一件事便是在 AddTests 类中声明了一个 DbContext 的成员变量, 就好像 SelectTest 类中的一样. 然后, 我们在 SetUp 方法中初始化 DbContext 来打开 SQL Server 数据库. SetUp 方法会在类中每一个单元测试执行之前调用.

然后, 在 InsertPersonWithAddresses 方法中, 我们创建了一个新的 Person 实体, 它含有两个子地址记录. 然后, 使用 DbSet<T>.Add 方法添加记录. 最后, 在方法中, 我在上下文上调用 SaveChanges 去持久化记录到数据库中. 一旦记录被保存, 我再使用 Entity Framework Core 5 来检索该数据, 然后在使用 Assert 调用来验证数据. 我验证了添加的 Person 的 Id 不是 0; 然后, 我验证了 person 有两个地址. 然后, 我验证了 person 中设置的字段的正确性. 最后, 我验证了 addresses 与我们使用 Add 方法, 插入到 SQL Server 数据库中的数据有相同的值.

TearDown 方法会发现新加入的 person 记录, 然后从数据库中删除它. 因此我们的测试记录在下一次单元测试执行时已被清理干净.

这个单元测试的方法常被称为 AAA, 即设置, 执行, 断言的形式. 这是单元测试的典型流程.

小结

本章我们涵盖了很多内容. 首先, 我介绍了如何使用 EF Core 5 插入根实体. 然后, 我介绍了如何在插入根记录时, 插入跟实体的子记录. 在那之后, 我们看到如何设置实体的主键值. 然后我们说到了如何设置实体非主键属性的默认值. 最后, 我介绍了如何使用 NUnit 测试添加一条新根实体, 以及其子记录. 下一章, 我将会介绍使用 EF Core 5如何更新数据库中的记录, 以及如何对其进行单元测试.

Last Updated:
Contributors: jk