C# Entity Framework高内存使用率,内存泄漏?

6
我有一个使用Entity Framework 6的小型MVC Web应用程序。在我的开发机上通过浏览主页(例如www.mywebsite.dev)启动应用程序时,应用程序池会启动并且页面按预期加载。
尽管主页非常轻量级,仅从数据库获取了一些内容(2个菜单、2个带文本的段落和一个包含3-4个对象的集合),但只要加载主页一次,应用程序池就已经大于200 MB(!)。
使用thisthis文章,我已经学会了如何分析管理内存,并且删除了一些阻止上下文处理的静态属性。DbContext已禁用延迟加载。
public class MyContext: DbContext
    {
        private readonly Dictionary<Type, EntitySetBase> _mappingCache = new Dictionary<Type, EntitySetBase>();

        #region dbset properties
        //Membership sets
        public IDbSet<UserProfile> UserProfiles { get; set; }
        public IDbSet<Project> Project { get; set; }
        public IDbSet<Portfolio> Portfolio { get; set; }
        public IDbSet<Menu> Menu { get; set; }
        public IDbSet<MenuItem> MenuItem { get; set; }
        public IDbSet<Page> Page { get; set; }
        public IDbSet<Component> Component { get; set; }
        public IDbSet<ComponentType> ComponentType { get; set; }
        public IDbSet<BlogCategory> BlogCategory { get; set; }
        public IDbSet<Blog> Blog { get; set; }
        public IDbSet<Caroussel> Carousel { get; set; }
        public IDbSet<CarouselItem> CarouselItem { get; set; }
        public IDbSet<Redirect> Redirect { get; set; }
        public IDbSet<TextBlock> TextBlock { get; set; }
        public IDbSet<Image> Image { get; set; }
        public IDbSet<ImageContent> ImageContent { get; set; }
        #endregion

        /// <summary>
        /// The constructor, we provide the connectionstring to be used to it's base class.
        /// </summary>
        public MyContext() : base("name=MyConnectionstring")
        {
            //Disable lazy loading by default!
            Configuration.LazyLoadingEnabled = false;

            Database.SetInitializer<BorloContext>(null);
        }

        //SOME OTHER CODE
}

我仍然看到许多存在于内存中的对象,我认为它们与实体框架的延迟加载有关。

Manage Memory usage

我已经设置了一个具有几个层次的网站;
  1. 控制器 - 通常的东西
  2. 服务 - 通过在控制器中使用语句来使用。服务是可处理的,并包含UnitOfWork。 UnitOfWork在服务的构造函数中初始化,并在服务本身被处理时处置。
  3. UnitOfWork - UnitOfWork类包含一个只读私有变量,其中包含上下文,以及一组实例化T类型的通用存储库的属性。同样,UnitOfWork是可处理的,并在调用Dispose方法时处置上下文。
  4. 通用存储库匹配接口,通过它的构造函数获取DbContext并通过接口提供基本的方法集。
以下是如何使用此功能的示例。
PartialController
public class PartialController : BaseController
    {
        //private readonly IGenericService<Menu> _menuService;
        //private readonly UnitOfWork _unitOfWork = new UnitOfWork();
        //private readonly MenuService _menuService;

        public PartialController()
        {
            //_menuService = new GenericService<Menu>();
            //_menuService = new MenuService();
        }

        /// <summary>
        /// Renders the mainmenu based on the correct systemname.
        /// </summary>
        [ChildActionOnly]
        public ActionResult MainMenu()
        {
            var viewModel = new MenuModel { MenuItems = new List<MenuItem>() };

            try
            {
                Menu menu;
                using (var service = ServiceFactory.GetMenuService())
                {
                    menu= service.GetBySystemName("MainMenu");
                }

                //Get the menuItems collection from somewhere
                if (menu.MenuItems != null && menu.MenuItems.Any())
                {
                    viewModel.MenuItems = menu.MenuItems.ToList();
                    return View(viewModel);
                }
            }
            catch (Exception exception)
            {
                //TODO: Make nice function of this and decide throwing or logging.
                if (exception.GetType().IsAssignableFrom(typeof(KeyNotFoundException)))
                {
                    throw;
                }
                else
                {
                    //TODO: Exception handling and logging
                    //TODO: If exception then redirect to 500-error page.
                }

            }

            return View(viewModel);
        }
    }

服务工厂

public class ServiceFactory
    {
        public static IService<Menu> GetMenuService()
        {
            return new MenuService();
        }
}

菜单服务

public class MenuService : BaseService, IService<Menu>
{
private readonly UnitOfWork _unitOfWork;
private bool _disposed;

public MenuService()
{
    if (_unitOfWork == null)
    {
        _unitOfWork = new UnitOfWork();
    }
}

/// <summary>
/// Retrieves the menu by the provided systemname.
/// </summary>
/// <param name="systemName">The systemname of the menu.</param>
/// <returns>The menu if found. Otherwise null</returns>
public Menu GetBySystemName(string systemName)
{
    var menu = new Menu();

    if (String.IsNullOrWhiteSpace(systemName)) throw new ArgumentNullException("systemName","Parameter is required.");

    if (Cache.HasItem(systemName))
    {
        menu = Cache.GetItem(systemName) as Menu;
    }
    else
    {
        var retrievedMenu = _unitOfWork.MenuRepository.GetSingle(m => m.SystemName.Equals(systemName), "MenuItems,MenuItems.Page");

        if (retrievedMenu == null) return menu;

        try
        {
            var exp = GenericRepository<CORE.Entities.MenuItem>.IsPublished();
            var menuItems = (exp != null) ?
                retrievedMenu.MenuItems.AsQueryable().Where(exp).Select(MenuTranslator.Translate).OrderBy(mi => mi.SortOrder).ToList() :
                retrievedMenu.MenuItems.Select(MenuTranslator.Translate).OrderBy(mi => mi.SortOrder).ToList();

            menu.MenuItems = menuItems;
        }
        catch (Exception)
        {
            //TODO: Logging
        }

        Cache.AddItem(systemName, menu, CachePriority.Default, CacheDuration.Short);
    }

    return menu;
}

public IEnumerable<Menu> Get()
{
    throw new NotImplementedException();
}

~MenuService()
{
    Dispose(false);
}

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            _unitOfWork.Dispose();
        }
    }
    _disposed = true;
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

}

通用仓储

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, IEntityObject

{ /// /// 使用的数据库上下文。 /// 内部 MyContext 上下文; }

/// <summary>
/// The loaded set of entities.
/// </summary>
internal DbSet<TEntity> DbSet;

/// <summary>
/// The constructor taking the databasecontext.
/// </summary>
/// <param name="context">The databasecontext to use.</param>
public GenericRepository(MyContext context)
{
    //Apply the context
    Context = context;

    //Set the entity type for the current dbset.
    DbSet = context.Set<TEntity>();
}
public IQueryable<TEntity> AsQueryable(bool publishedItemsOnly = true)
{
    if (!publishedItemsOnly) return DbSet;
    try
    {
        return DbSet.Where(IsPublished());
    }
    catch (Exception)
    {
        //TODO: Logging
    }

    return DbSet;
}

/// <summary>
/// Gets a list of items matching the specified filter, order by and included properties.
/// </summary>
/// <param name="filter">The filter to apply.</param>
/// <param name="includeProperties">The properties to include to apply eager loading.</param>
/// <param name="publishedItemsOnly">True if only publish and active items should be included, otherwise false.</param>
/// <returns>A collection of entities matching the condition.</returns>
public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter, string includeProperties, bool publishedItemsOnly)
{
    var query = AsQueryable(publishedItemsOnly);

    if (filter != null)
    {
        query = query.Where(filter);
    }


    if (String.IsNullOrWhiteSpace(includeProperties))
        return query;

    //Include all properties to the dbset to enable eager loading.
    query = includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));

    return query;
}

}

简言之,我的代码/情况可能导致加载首页时使用了惊人的200MB或更多内存?我注意到一个奇怪的事情是,在页面加载前,内存从111MB跳到232MB(如下例);

Large memory IIS

编辑 使用 dotMemory 进行跟踪的结果

enter image description here

编辑2 在加载主页后,以下是结果。主页现在为空,在全局asax中只调用了一个服务。我将页面保持打开一段时间然后刷新,导致出现了所有的峰值。 性能分析1

以下是更详细的结果,显然有很多字符串占用了大量内存..? 性能分析详情

编辑3 来自dotMemory的不同视图 输入图像描述 输入图像描述


1
@UweKeim 我刚试了一下,但是我从结果中没有得到任何有用的东西(或者是我的知识不够..;-)) 我试了dotMemory,我在问题中附上了结果 - Rob
1
@UweKeim,嗯,说得好。我今晚会再做一些调试和分析。但是如果有人有建议,我非常乐意听取 :) - Rob
1
好的,我已经发现使用该服务很可能是问题的原因,只是我不知道为什么。 - Rob
1
仅加载主页时,使用了惊人的200 MB或更多。获取快照,打开快照概述,查看哪些类型的对象占用了大部分内存。查看哪些对象独占了大部分内存。在页面加载之前,内存从111 MB跳到232 MB。在跳跃之前和之后获取一个快照,查看“新”列中的单元格上有哪些“新”对象。然后,您可以以任何方式调查它们。 - Ed Pavlov
1
@Rob,第二张截图是“内存流量”视图。它显示了从分析开始到获取快照时创建的所有对象。正如您在此视图中所看到的,大约创建了5MB的字符串,其中大约有3.5MB被回收(删除)。您不需要“内存流量”视图,而是需要“快照”视图,在该视图中,dotMemory会显示获取快照时关于活动对象的摘要信息。要打开它,请单击“内存快照”区域中表示快照的白色瓷砖上的“快照#1”链接。 - Ed Pavlov
显示剩余7条评论
1个回答

4
所以,图像现在更加清晰了。 dotMemory显示,您的应用程序仅占用9Mb的内存,我们可以在快照视图中看到这一点。这也得到了内存流量视图的确认。从分析开始分配了约73Mb,到快照#1点已经收集了约65Mb。
那么实时数据图表上显示的总内存使用情况呢?抱歉我之前没有意识到,大部分应用程序内存使用情况都是0代堆。(我还错过了屏幕上快照瓷砖上只使用了约8Mb的应用程序)。
“Gen 0堆大小”显示了0代堆中可以分配的最大字节数;它不表示当前在0代堆中分配的字节数。 http://msdn.microsoft.com/en-us/library/x2tyfybc(v=vs.110).aspx 对于我的口味来说,“Gen 0堆大小”看起来异常大,但这是.net垃圾回收器的内部细节,它有权这样做。
我想建议您的应用程序正在运行在内存较大和/或CPU缓存较大的计算机上。但它也可能是ASP服务器实现的特殊方面。
结论-您的应用程序内存使用没有问题 :) 至少在加载主页时。
附言:我建议观看dotMemory视频教程,以便学习如何使用它。

感谢您的积极回复!如果我理解正确,它只是可以分配的最大内存量?那么为什么每次刷新页面时它都会稍微增加一点呢? 只需刷新一两次主页(包括内容),内存就超过了200 MB。请参见此处(http://imgur.com/5FydyhD)以获取详细信息。这台机器确实运行了很多内存。它是一台重型笔记本电脑,配备16GB的RAM和Core i7处理器。因此,它只是分配了大量内存。 - Rob
@Rob "just the maximum amount of memory that can be allocated" - 可以在垃圾回收发生之前(在Gen 0中)分配的最大内存量。当Gen 0堆栈已满时,垃圾收集器开始工作,删除所有垃圾并将所有幸存对象提升到Gen 1。由于您的应用程序运行在笔记本电脑上,而不是服务器硬件上,我猜测如此大的Gen 0堆栈大小是ASP服务器的实现细节,但我对ASP不是很熟悉,无法证明这一点。 - Ed Pavlov
1
目前我会接受问题已经解决的事实。dotMemory指出了一些其他的问题,这些问题肯定会改善情况。我将继续开发并在测试环境中观察情况。感谢您的帮助! - Rob

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