EntityFramework应该是那么慢吗?

时间:2016-12-11 18:24:36

标签: c# .net entity-framework asp.net-web-api asp.net-web-api2

我正在尝试为我的客户端移动应用创建一个简单的API。我创建了新的空Web API项目和一个动作,但它非常慢:

[HttpGet, Route("manga")]
public List<MangaDTO> GetList()
{
    using (var mc = new MangaContext()) // Sometimes takes 10s to instantiate the context
    {
        var list = mc.Mangas.ToList(); // Takes ~3.5s to get all
        var mangas = new List<MangaDTO>(); 
        foreach (var o in list) // This loop takes literally forever. ~35s on my server.
            mangas.Add(new MangaDTO(o));
        return mangas;
    }
}

有没有更有效的方法来实现我想要的?获取响应需要花费太长时间,当我将其部署到远程服务器时,它甚至不会显示结果 - 只是超时错误。

我可以做些什么来加快速度?另外,这是MangaContext类:

[DbConfigurationType(typeof(MySql.Data.Entity.MySqlEFConfiguration))]
public class MangaContext : DbContext
{
    private static MangaContext current;

    public static MangaContext Current
    {
        get { if (current == null) current = new MangaContext(); return current; }
    }

    public MangaContext()
    {
        Database.CreateIfNotExists();
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MangaContext>());
    }

    public virtual DbSet<Manga> Mangas { get; set; }
    public virtual DbSet<Genre> Genres { get; set; }
    public virtual DbSet<Alias> Aliases { get; set; }
}

我在这里遗漏了什么吗?因为我怀疑实例化应该需要~10s。

编辑:

我从Context的构造函数中删除了Database.CreateIfNotExists()调用。另外,这是MangaDTO类:

public class MangaDTO
{
    public MangaDTO(Manga m)
    {
        id = m.Id;
        name = m.Name;
        genres = m.Genres.Select(o => o.Name).ToList();
        author = m.Author;
        artist = m.Artist;
        score = m.Score;
        year = m.Year;
        aliases = m.Aliases.Select(o => o.Name).ToList();
    }

    public int id { get; set; }
    public string name { get; set; }
    public List<string> genres { get; set; }
    public string author { get; set; }
    public string artist { get; set; }
    public int year { get; set; }
    public List<string> aliases { get; set; }
    public int score { get; set; }
}

......和模特:

public class Manga
{
    public Manga() { }
    public Manga(string name, string desc, string author, string artist, int year, int status, int chapters, string url)
    {
        Name = name;
        Description = desc;
        Author = author;
        Artist = artist;
        Year = year;
        Status = status;
        Url = url;
        Aliases = new List<Alias>();
        Genres = new List<Genre>();
        ChaptersCount = chapters;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Author { get; set; }
    public string Artist { get; set; }
    public string Url { get; set; }
    public int Year { get; set; }
    public int Status { get; set; }

    public int ChaptersCount { get; set; }
    public int Score { get; set; }

    public virtual List<Alias> Aliases { get; set; }
    public virtual List<Genre> Genres { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

public class Alias
{
    public Alias() { }
    public Alias(string name, Manga manga)
    {
        Name = name;
        Manga = manga;
    }

    public int Id { get; set; }
    public string Name { get; set; }

    public virtual Manga Manga { get; set; }
}

public class Genre
{
    public Genre() { }
    public Genre(string name)
    {
        Name = name;
        Mangas = new List<Manga>();
    }

    public int Id { get; set; }
    public string Name { get; set; }

    public virtual List<Manga> Mangas { get; set; }
}

编辑2

现在它在我的远程服务器上引发异常,但是

{"Message":"An error has occurred.","ExceptionMessage":"An error occurred while executing the command definition. See the inner exception for details.","ExceptionType":"System.Data.Entity.Core.EntityCommandExecutionException","StackTrace":" at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)\r\n at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan.Execute[TResultType](ObjectContext context, ObjectParameterCollection parameterValues)\r\n at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)\r\n at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassb.b__9()\r\n at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)\r\n at System.Data.Entity.Core.Objects.ObjectQuery`1..GetEnumerator>b__0()\r\n at System.Lazy`1.CreateValue()\r\n at System.Lazy`1.LazyInitValue()\r\n at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()\r\n at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)\r\n at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)\r\n at MangaAPI.Controllers.MangaController.GetList() in C:\\Users\\mrrey\\OneDrive\\Documents\\Visual Studio 2015\\Projects\\XYZ Manga Reader\\MangaAPI\\Controllers\\MangaController.cs:line 39\r\n at lambda_method(Closure , Object , Object[] )\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.b__9(Object instance, Object[] methodParameters)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.","ExceptionType":"MySql.Data.MySqlClient.MySqlException","StackTrace":" at MySql.Data.MySqlClient.ExceptionInterceptor.Throw(Exception exception)\r\n at MySql.Data.MySqlClient.MySqlConnection.HandleTimeoutOrThreadAbort(Exception ex)\r\n at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)\r\n at MySql.Data.Entity.EFMySqlCommand.ExecuteDbDataReader(CommandBehavior behavior)\r\n at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TInterceptionContext,TResult](Func`1 operation, TInterceptionContext interceptionContext, Action`1 executing, Action`1 executed)\r\n at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.Reader(DbCommand command, DbCommandInterceptionContext interceptionContext)\r\n at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","ExceptionType":"System.TimeoutException","StackTrace":" at MySql.Data.Common.MyNetworkStream.Read(Byte[] buffer, Int32 offset, Int32 count)\r\n at MySql.Data.MySqlClient.TimedStream.Read(Byte[] buffer, Int32 offset, Int32 count)\r\n at System.IO.BufferedStream.Read(Byte[] array, Int32 offset, Int32 count)\r\n at MySql.Data.MySqlClient.MySqlStream.ReadFully(Stream stream, Byte[] buffer, Int32 offset, Int32 count)\r\n at MySql.Data.MySqlClient.MySqlStream.LoadPacket()\r\n at MySql.Data.MySqlClient.MySqlStream.ReadPacket()\r\n at MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int64& insertedId)\r\n at MySql.Data.MySqlClient.Driver.NextResult(Int32 statementId, Boolean force)\r\n at MySql.Data.MySqlClient.MySqlDataReader.NextResult()\r\n at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior)","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","ExceptionType":"System.IO.IOException","StackTrace":" at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)\r\n at MySql.Data.Common.MyNetworkStream.Read(Byte[] buffer, Int32 offset, Int32 count)","InnerException":{"Message":"An error has occurred.","ExceptionMessage":"A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond","ExceptionType":"System.Net.Sockets.SocketException","StackTrace":" at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)"}}}}}

1 个答案:

答案 0 :(得分:2)

我不能说为什么实例化一个新的上下文需要这么长时间,除了最终使用的构造函数和db初始化程序中的代码,所以你可以尝试删除它们(替换为Database.SetInitializer(null);)并查看是否有帮助

但是查询肯定会加速。目前,您正在将整个Manga表加载到内存中,然后为每条记录加载,还有两个查询由

执行
genres = m.Genres.Select(o => o.Name).ToList();

aliases = m.Aliases.Select(o => o.Name).ToList();

因为m.Genresm.Aliases是延迟加载的导航属性。另请注意,它们首先加载所有相关对象,然后LINQ to Objects在内存中执行Select(o => o.Name)

你可以通过这样直接投射到MangaDTO来避免所有这些(单个SLQ查询,没有实体跟踪开销):

using (var mc = new MangaContext())
{
    var query = mc.Mangas
        .Select(m => new MangaDTO
        {
            id = m.Id,
            name = m.Name,
            genres = m.Genres.Select(o => o.Name).ToList(),
            author = m.Author,
            artist = m.Artist,
            score = m.Score,
            year = m.Year,
            aliases = m.Aliases.Select(o => o.Name).ToList(),
        });
    var mangas = query.ToList();
    return mangas;
}

确保将无参数构造函数添加到MangaDTO。您现在可以衡量ToList来电,看看需要多长时间。