2020-05-24 - EFCore保存数据提纲

2020-05-24  本文已影响0人  daiwei_9b9c

基本保存

相关数据

级联删除

描述一种允许在删除某行时自动触发删除相关行的特性

#删除主实体的代码

var blog = context.Blogs.Include(b => b.Posts).First();
context.Remove(blog);
context.SaveChanges();

1. (可选关系) 外键可为null时的级联删除

a. Cascade -- 删除实体和子实体
b. ClientSetNull(默认)

内存中相关实体外键属性变为null, SaveChanges 时会变更数据库;
但如果存在未加载的关联子实体,可能会抛出异常,因为引用的主实体不存在

c. SetNull, 外键属性设置为 null

内存中相关实体外键属性变为null, SaveChanges 时内存中关联实体将设置外键为 null;
如果存在未加载的关联子实体,数据库支持时会设置关联子实体的外键为null,数据库不支持时引发异常

d. Restrict, 不进行任何改变

EFCore 抛出异常, 子实体的外键不发生改变, 引用了删除的实体

2. 必选关系 ( 外键不可为null时) 的级联删除

DeleteBehavior 在 OnDelete 中的值
a. Cascade(默认) 删除相关实体
b. ClientSetNull,
SQL抛出异常, 不可以设置外键字段为 null
c. SetNull, ,
SQL抛出异常, 不可以设置外键字段为 null
d. Restrict, 不进行任何改变
EFCore 抛出异常, 子实体的外键不发生改变, 引用了删除的实体

删除孤立项代码

var blog = context.Blogs.Include(b => b.Posts).First();
blog.Posts.Clear();
context.SaveChanges();

a. DeleteBehavior.Cascade ( 无论是可选还是必选)
子实体从数据库删除
b. 具有必选关系的 DeleteBehavior.ClientSetNull 或 DeleteBehavior.SetNull
SQL执行异常,无法设置 BlogId字段为 null
c. 具有可选关系的 DeleteBehavior.ClientSetNull 或 DeleteBehavior.SetNull
Post 的 BlogId 字段设置为 null
d. 具有必选或可选关系的 DeleteBehavior.Restrict
EFCore 抛出异常, Post有外键,但是未指向内存中的 Blog 对象
Blog '1' is in state Unchanged with 2 posts referenced.
Post '1' is in state Modified with FK '1' and no reference to a blog.

并发冲突

“当前值” 是应用程序尝试写入数据库的值。
“原始值” 是在进行任何编辑之前最初从数据库中检索的值。
“数据库值” 是当前存储在数据库中的值。

在 SaveChanges 期间捕获 DbUpdateConcurrencyException。
使用 DbUpdateConcurrencyException.Entries 为受影响的实体准备一组新更改。
刷新并发令牌的原始值以反映数据库中的当前值。
重试该过程,直到不发生任何冲突。

事务

var connectionString = @"Server=(localdb)\mssqllocaldb;Database=EFSaving.Transactions;Trusted_Connection=True;ConnectRetryCount=0";
var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(new SqlConnection(connectionString))
                .Options;   
using (var context1 = new BloggingContext(options))
{
        using (var transaction = context1.Database.BeginTransaction())
        {
           using (var context2 = new BloggingContext(options))
           {
                context2.Database.UseTransaction(transaction.GetDbTransaction());
          }  
      }
}
public class BloggingContext : DbContext
{
            public BloggingContext(DbContextOptions<BloggingContext> options)
                : base(options)
            { }
            ....
}
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (var transaction = connection.BeginTransaction())
    {
           var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
          using (var context = new BloggingContext(options))
            {
                context.Database.UseTransaction(transaction);  //使用外部的事务
            }
            transaction.Commit();
    }
}
using (var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
     using (var connection = new SqlConnection(connectionString))
     {
           connection.Open();
           var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
          using (var context = new BloggingContext(options))
            {
                context.Database.UseTransaction(transaction);  //使用外部的事务
            }
            scope.Complete();
     }
}

b. 在显式事务中登记
context.Database.EnlistTransaction(transaction)

using (var transaction = new CommittableTransaction(
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
         var connection = new SqlConnection(connectionString);
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;
        using (var context = new BloggingContext(options))
        {
            context.Database.OpenConnection();
            context.Database.EnlistTransaction(transaction);
        }
        transaction.Commit();
}

异步保存

必须调用 await 否则, DbContext可能会被dispose

using (var context = new BloggingContext())
    {
        var blog = new Blog { Url = url };
        context.Blogs.Add(blog);
        await context.SaveChangesAsync();
    }

断开连接的实体

实例不是由 DbContext 从数据库查询而来,但是需要保存到数据库
DbContext实例需要知道实体是新实体(应插入)还是现有实体(应更新)

public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }
    context.SaveChanges();
}

SetValues 仅将与跟踪实体中的属性具有不同值的属性标记为“已修改”。这意味着当发送更新时,只会更新实际发生更改的列。 (如果未发生更改,则根本不会发送任何更新。)

设置已生成属性的显式值

CREATE TRIGGER [dbo].[Employees_UPDATE] ON [dbo].[Employees]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    -- 避免被下面的 update 循环执行此触发器;              
    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
                  
    IF UPDATE(Salary) AND NOT Update(LastPayRaise) -- 是否更新了 Salary 字段
    BEGIN
        DECLARE @Id INT
        DECLARE @OldSalary INT
        DECLARE @NewSalary INT
          
        SELECT @Id = INSERTED.EmployeeId, @NewSalary = Salary        
        FROM INSERTED
          
        SELECT @OldSalary = Salary        
        FROM deleted
          
        IF @NewSalary > @OldSalary
        BEGIN
            UPDATE dbo.Employees
            SET LastPayRaise = CONVERT(date, GETDATE())
            WHERE EmployeeId = @Id
        END
    END
END

TRIGGER_NESTLEVEL --
When no parameters are specified, TRIGGER_NESTLEVEL returns the total number of triggers on the call stack. This includes itself.
无参数时,返回此语句执行时在调用堆栈中的触发器的数量;
SQLSERVER最大支持 32层触发器嵌套;

上一篇 下一篇

猜你喜欢

热点阅读