【问题标题】:C# Entity Framework high memory usage, memory leak?C# Entity Framework 高内存使用,内存泄漏?
【发布时间】:2014-11-13 11:50:26
【问题描述】:

我有一个使用 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
}

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

我已经为网站设置了几层;

  1. 控制器 - 常用的东西
  2. Service - 处理了控制器中使用的 using 语句。这些服务是一次性的并包含一个 UnitOfWork。 UnitOfWork 在服务的构造函数中初始化,并在服务本身被释放时释放。
  3. UnitOfWOrk - UnitOfWork 类包含一个包含上下文的只读私有变量,以及一组实例化 T 类型的通用存储库的属性。同样,UnitOfWork 是一次性的,它在调用 Dispose 方法时释放上下文。
  4. 通用存储库匹配一个接口,将 DbContext 当作它的构造函数,并通过一个接口提供一组基本的方法。

下面是如何使用它的示例。

部分控制器

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);
}

}

GenericRepository

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;
}

}

长话短说。在我的代码/情况下,什么可能导致仅加载主页时使用惊人的 200 MB 或更多的问题?我注意到的一件奇怪的事情是,在下面的示例中,就在页面加载之前,内存从 111 MB 跳转到了 232 MB;

编辑 dotMemory 的跟踪结果

编辑 2 在我加载主页后的结果下方。主页现在是空的,并且在全局 asax 中只调用了一项服务。我将页面保持打开了一段时间,然后刷新,导致所有峰值。

下面是更详细的结果,显然很多字符串占用了大量内存..?

编辑 3 与 dotMemory 不同的视图

【问题讨论】:

  • @UweKeim 我刚刚尝试了那个,但我无法从结果中得到任何有用的东西(或者这是我的知识..;-))我尝试了 dotMemory,我将结果附加到问题
  • @UweKeim,嗯,好点。今晚我会做更多的调试和分析。但是,如果有人有建议,我会非常乐意接受:)
  • 好的,我发现服务的使用最有可能导致问题,只是我不知道为什么..
  • "当只加载主页时,使用了惊人的 200 MB 或更多" - 获取快照,打开 snapshot overview,查看哪些类型的对象占用了大部分内存。看看哪些对象专门保留了大部分内存。 “在加载页面之前,内存从 111 MB 跳转到 232 MB”在跳转之前和之后获取快照,查看什么是“新”对象(单击“全部”行的“新”列中的单元格)。然后,您将能够以任何您想要的方式调查它们。
  • 会伤害的一件事是将上下文/UoW 对象分配给类字段/道具。那些家伙很重,真的适合本地 var 使用。

标签: c# asp.net-mvc entity-framework memory memory-leaks


【解决方案1】:

所以,现在图像更清晰了。 dotMemory 显示,您的应用程序仅占用 9Mb 内存,我们可以在 Snapshot 视图中看到这一点。内存流量视图也证实了这一点。大约 73Mb 从分析开始分配,大约 65Mb 已经收集到快照 #1 点。

实时数据图表上显示的总内存使用情况如何,抱歉我之前没有意识到你的应用程序内存使用量最多的是第 0 代堆。 (我还错过了您的应用在此屏幕上的快照磁贴上仅使用了 ~8Mb)。

Gen 0 堆大小显示可以分配的最大字节数 第0代;它不表示当前字节数 在第 0 代分配http://msdn.microsoft.com/en-us/library/x2tyfybc(v=vs.110).aspx

Gen 0 堆大小在我看来异常大,但它是 .net 垃圾收集器的内部细节,它有权这样做。

我冒昧地建议您的应用在具有大量 RAM 和/或大 CPU 缓存的计算机上运行。但它也可能是 ASP 服务器实现的特殊方面。

结论 - 您的应用程序内存使用没有问题 :) 至少在加载主页时。

附:我建议观看dotMemory video tutorials,以了解如何使用它

【讨论】:

  • 感谢您的好评!因此,如果我理解正确,这只是可以分配的最大内存量吗?那么为什么它在每次页面刷新时都会增长一点点?重新刷新一两个主页(这次包括内容)占用超过 200 MB 再次,请参阅此 (imgur.com/5FydyhD) 了解详细信息.. 机器确实运行了大量内存。这是一款重型笔记本电脑,配备 16GB 内存和 i7 核心。因此,它只是分配了大量内存。
  • @Rob “只是可以分配的最大内存量” - 可以分配(在 Gen 0 中)在垃圾收集发生之前。垃圾收集器开始工作,当第 0 代堆满时,它会删除所有垃圾并将所有幸存的对象提升到第 1 代。由于您的应用程序在笔记本电脑上运行,而不是在服务器硬件上运行,我猜第 0 代堆的大小如此之大是 ASP 服务器的实现细节,但我对 ASP 不是很熟悉来证明这一点。
  • 好吧,现在我会接受问题已解决。 dotMemory 向我指出了一些其他明显改进的东西。我将继续开发,看看它在测试环境中是如何进行的。感谢您的所有帮助!
猜你喜欢
  • 2018-01-11
  • 1970-01-01
  • 2016-01-27
  • 2010-11-11
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多