实体框架SaveChanges()与SaveChangesAsync()和Find()与FindAsync()

时间:2015-05-05 01:26:08

标签: c# entity-framework async-await

我一直在寻找上述2对之间的差异,但没有发现任何文章清楚地解释它以及何时使用这些。

那么SaveChanges()SaveChangesAsync()之间有什么区别? 在Find()FindAsync()之间?

在服务器端,当我们使用Async方法时,我们还需要添加await。因此,我不认为它在服务器端是异步的。

它是否仅有助于阻止客户端浏览器上的UI阻止?或者它们之间是否有任何利弊?

3 个答案:

答案 0 :(得分:134)

只要您需要在远程服务器上执行操作,程序就会生成请求,发送请求,然后等待响应。我将以SaveChanges()SaveChangesAsync()为例,但这同样适用于Find()FindAsync()

假设您有一个需要添加到数据库中的100多个项目的列表myList。要插入它,你的函数看起来像这样:

using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}

首先创建MyEDM的实例,将列表myList添加到表MyTable,然后调用SaveChanges()将更改保留到数据库。它按照你想要的方式工作,记录得到提交,但是你的程序在提交完成之前不能做任何事情。这可能需要很长时间,具体取决于您提交的内容。如果您要对记录进行更改,实体必须一次提交这些更改(我曾经保存了2分钟进行更新)!

要解决此问题,您可以执行以下两项操作之一。首先,您可以启动一个新线程来处理插入。虽然这将释放调用线程以继续执行,但是您创建了一个新线程,它只是坐在那里等待。不需要那个开销,这就是async await模式解决的问题。

对于I / O操作,await很快成为您最好的朋友。从上面的代码部分,我们可以将其修改为:

using(var context = new MyEDM())
{
    Console.WriteLine("Save Starting");
    context.MyTable.AddRange(myList);
    await context.SaveChangesAsync();
    Console.WriteLine("Save Complete");
}

这是一个非常小的变化,但会对代码的效率和性能产生深远的影响。那会发生什么?代码的开头是相同的,您创建MyEDM的实例并将myList添加到MyTable。但是当你调用await context.SaveChangesAsync()时,代码的执行将返回到调用函数!因此,当您等待所有这些记录提交时,您的代码可以继续执行。假设包含上述代码的函数具有public async Task SaveRecords(List<MyTable> saveList)的签名,调用函数可能如下所示:

public async Task MyCallingFunction()
{
    Console.WriteLine("Function Starting");
    Task saveTask = SaveRecords(GenerateNewRecords());

    for(int i = 0; i < 1000; i++){
        Console.WriteLine("Continuing to execute!");
    }

    await saveTask;
    Console.Log("Function Complete");
}

为什么你会有这样的功能,我不知道,但它输出的内容显示了async await的工作原理。首先让我们回顾一下发生的事情。

执行进入MyCallingFunctionFunction Starting然后Save Starting被写入控制台,然后调用函数SaveChangesAsync()。此时,执行返回MyCallingFunction并进入for循环写入'Continuing to Execute'最多1000次。当SaveChangesAsync()完成时,执行返回SaveRecords函数,将Save Complete写入控制台。一旦SaveRecords中的所有内容完成,执行将在MyCallingFunction中继续执行,直到SaveChangesAsync()完成。困惑?以下是输出示例:

Function Starting
Save Starting
Continuing to execute!
Continuing to execute!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Save Complete!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Function Complete!

或者也许:

Function Starting
Save Starting
Continuing to execute!
Continuing to execute!
Save Complete!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Function Complete!

这就是async await的美丽,你的代码可以在你等待完成的时候继续运行。实际上,你的函数更像是你的调用函数:

public async Task MyCallingFunction()
{
    List<Task> myTasks = new List<Task>();
    myTasks.Add(SaveRecords(GenerateNewRecords()));
    myTasks.Add(SaveRecords2(GenerateNewRecords2()));
    myTasks.Add(SaveRecords3(GenerateNewRecords3()));
    myTasks.Add(SaveRecords4(GenerateNewRecords4()));

    await Task.WhenAll(myTasks.ToArray());
}

在这里,您有四个不同的保存记录功能同时MyCallingFunction使用async await比使用系列调用各个SaveRecords函数要快得多。

我还没有涉及的一件事是await关键字。这样做会阻止当前函数执行,直到等待完成的Task为止。因此,对于原始MyCallingFunction,在Function Complete函数完成之前,行SaveRecords将不会写入控制台。

简而言之,如果您可以选择使用async await,那么您应该会大大提高应用程序的性能。

答案 1 :(得分:2)

此声明不正确:

  

在服务器端,当我们使用Async方法时,我们还需要添加await。

您无需添加&#34;等待&#34;。 &#34;等待&#34;它只是C#中一个方便的关键字,它允许您在调用后编写更多代码行,而其他行只会在Save操作完成后执行。但正如您所指出的那样,您只需通过调用SaveChanges而不是SaveChangesAsync即可完成此任务。

但从根本上说,异步呼叫远不止于此。这里的想法是,如果在Save操作正在进行时你可以做其他工作(在服务器上),那么你应该使用SaveChangesAsync。不要使用&#34;等待&#34;。只需调用SaveChangesAsync,然后继续并行执行其他操作。这可能包括在Web应用程序中,甚至在Save完成之前就向客户端返回响应。但是,当然,您仍然需要检查Save的最终结果,以便在失败时,您可以将其传达给您的用户,或以某种方式记录。

答案 2 :(得分:0)

我剩下的解释将基于以下代码片段。

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

public static class Program
{
    const int N = 20;
    static readonly object obj = new object();
    static int counter;

    public static void Job(ConsoleColor color, int multiplier = 1)
    {
        for (long i = 0; i < N * multiplier; i++)
        {
            lock (obj)
            {
                counter++;
                ForegroundColor = color;
                Write($"{Thread.CurrentThread.ManagedThreadId}");
                if (counter % N == 0) WriteLine();
                ResetColor();
            }
            Thread.Sleep(N);
        }
    }

    static async Task JobAsync()
    {
       // intentionally removed
    }

    public static async Task Main()
    {
       // intentionally removed
    }
}

案例1

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    Job(ConsoleColor.Green, 2);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

enter image description here

备注:由于JobAsync的同步部分(绿色)旋转得比任务t(红色)更长,因此任务t在{{1}处已经完成}。结果,延续(蓝色)在与绿色线程相同的线程上运行。 await t(白色)的同步部分将在绿色部分完成旋转后旋转。这就是为什么异步方法中的同步部分有问题的原因。

案例2

Main

enter image description here

备注:这种情况与第一种情况相反。 static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 2)); Job(ConsoleColor.Green, 1); await t; Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; } 的同步部分(绿色)旋转得比任务JobAsync(红色)短,则任务tt点尚未完成。结果,延续(蓝色)在与绿色线程不同的线程上运行。 await t(白色)的同步部分在绿色部分完成旋转后仍然旋转。

案例3

Main

enter image description here

备注:这种情况将解决先前情况下有关异步方法中的同步部分的问题。任务static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 1)); await t; Job(ConsoleColor.Green, 1); Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; } 立即等待。结果,延续(蓝色)在与绿色线程不同的线程上运行。 t(白色)的同步部分将立即平行于Main旋转。

如果要添加其他案例,请随时进行编辑。