#c# #.net #performance #entity-framework #linq-to-entities
#c# #.net #Производительность #entity-framework #linq-to-entities
Вопрос:
Вот изображение из профилировщика памяти ANTS. Он видит, что в памяти хранится много объектов. Как я могу определить, что я делаю неправильно?
**UPDATE**
Вот мои классы репозитория:
public class Repository<T> : IRepository<T> where T : class, IDataEntity
{
ObjectContext _context;
IObjectSet<T> _objectSet;
readonly string _entitySetName;
readonly string[] _keyNames;
private ObjectContext Context
{
get
{
if (_context == null)
{
_context = GetCurrentUnitOfWork<EFUnitOfWork>().Context;
}
return _context;
}
}
private IObjectSet<T> ObjectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this.Context.CreateObjectSet<T>();
}
return _objectSet;
}
}
public TUnitOfWork GetCurrentUnitOfWork<TUnitOfWork>() where TUnitOfWork : IUnitOfWork
{
return (TUnitOfWork)UnitOfWork.Current;
}
public virtual IEnumerable<T> GetQuery()
{
return ObjectSet;
}
public virtual IEnumerable<T> GetQuery(params Expression<Func<T, object>>[] includes)
{
return ObjectSet.IncludeMultiple(includes);
}
public virtual IEnumerable<T> GetQuery(
IEnumerable<Expression<Func<T, bool>>> filters,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy,
IEnumerable<Expression<Func<T, object>>> includes)
{
IQueryable<T> _query = ObjectSet;
if (filters != null)
{
foreach (var filter in filters)
{
_query = _query.Where(filter);
}
}
if (includes != null amp;amp; includes.Count() > 0)
{
_query = _query.IncludeMultiple(includes.ToArray());
}
if (orderBy != null)
{
_query = orderBy(_query);
}
return _query;
}
public virtual IPaged<T> GetQuery(
IEnumerable<Expression<Func<T, bool>>> filters,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy,
int pageNumber, int pageSize,
IEnumerable<Expression<Func<T, object>>> includes)
{
IQueryable<T> _query = ObjectSet;
if (filters != null)
{
foreach (var filter in filters)
{
_query = _query.Where(filter);
}
}
if (orderBy != null)
{
_query = orderBy(_query);
}
IPaged<T> page = new Paged<T>(_query, pageNumber, pageSize, includes);
return page;
}
public virtual void Insert(T entity)
{
this.ObjectSet.AddObject(entity);
}
public virtual void Delete(T entity)
{
if (entity is ISoftDeletable)
{
((ISoftDeletable)entity).IsDeleted = true;
//Update(entity);
}
else
{
this.ObjectSet.DeleteObject(entity);
}
}
public virtual void Attach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == false)
{
this.ObjectSet.Attach(entity);
}
}
public virtual void Detach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == true)
{
this.ObjectSet.Detach(entity);
}
}
}
Теперь, если у меня есть класс A, который содержит записи из таблицы A, я также создаю class:
public class ARepository:BaseRepository<A> {
// Implementation of A's queries and specific db operations
}
Вот мой класс EFUnitOfWork:
public class EFUnitOfWork : IUnitOfWork, IDisposable
{
public ObjectContext Context { get; private set; }
public EFUnitOfWork(ObjectContext context)
{
Context = context;
context.ContextOptions.LazyLoadingEnabled = true;
}
public void Commit()
{
Context.SaveChanges();
}
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
}
GC.SuppressFinalize(this);
}
}
И класс UnitOfWork:
public static class UnitOfWork
{
private const string HTTPCONTEXTKEY = "MyProj.Domain.Business.Repository.HttpContext.Key";
private static IUnitOfWorkFactory _unitOfWorkFactory;
private static readonly Hashtable _threads = new Hashtable();
public static void Commit()
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork != null)
{
unitOfWork.Commit();
}
}
public static IUnitOfWork Current
{
get
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork == null)
{
_unitOfWorkFactory = ObjectFactory.GetInstance<IUnitOfWorkFactory>();
unitOfWork = _unitOfWorkFactory.Create();
SaveUnitOfWork(unitOfWork);
}
return unitOfWork;
}
}
private static IUnitOfWork GetUnitOfWork()
{
if (HttpContext.Current != null)
{
if (HttpContext.Current.Items.Contains(HTTPCONTEXTKEY))
{
return (IUnitOfWork)HttpContext.Current.Items[HTTPCONTEXTKEY];
}
return null;
}
else
{
Thread thread = Thread.CurrentThread;
if (string.IsNullOrEmpty(thread.Name))
{
thread.Name = Guid.NewGuid().ToString();
return null;
}
else
{
lock (_threads.SyncRoot)
{
return (IUnitOfWork)_threads[Thread.CurrentThread.Name];
}
}
}
}
private static void SaveUnitOfWork(IUnitOfWork unitOfWork)
{
if (HttpContext.Current != null)
{
HttpContext.Current.Items[HTTPCONTEXTKEY] = unitOfWork;
}
else
{
lock(_threads.SyncRoot)
{
_threads[Thread.CurrentThread.Name] = unitOfWork;
}
}
}
}
Here is how I use this:
public class TaskPriceRepository : BaseRepository<TaskPrice>
{
public void Set(TaskPrice entity)
{
TaskPrice taskPrice = GetQuery().SingleOrDefault(x => x.TaskId == entity.TaskId);
if (taskPrice != null)
{
CommonUtils.CopyObject<TaskPrice>(entity, ref taskPrice);
}
else
{
this.Insert(entity);
}
}
}
public class BranchRepository : BaseRepository<Branch>
{
public IList<Branch> GetBranchesList(Guid companyId, long? branchId, string branchName)
{
return Repository.GetQuery().
Where(b => companyId == b.CompanyId).
Where(b => b.IsDeleted == false).
Where(b => !branchId.HasValue || b.BranchId.Equals(branchId.Value)).
Where(b => branchName == null || b.BranchName.Contains(branchName)).
ToList();
}
}
[WebMethod]
public void SetTaskPrice(TaskPriceDTO taskPrice)
{
TaskPrice tp = taskPrice.ToEntity();
TaskPriceRepository rep = new TaskPriceRepository();
rep.Set(tp);
UnitOfWork.Commit();
}
[WebMethod]
public IList<Branch> GetBranchesList()
{
BranchRepository rep = new BranchRepository();
return rep.GetBranchesList(m_User.UserCompany.CompanyId, null, null).ToList();
}
Я надеюсь, что этой информации достаточно, чтобы помочь мне решить проблему. Спасибо.
UPDATE 2
Существует также UnitOfWorkFactory, который инициализирует UnitOfWork:
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private static Func<ObjectContext> _objectContextDelegate;
private static readonly Object _lockObject = new object();
public static void SetObjectContext(Func<ObjectContext> objectContextDelegate)
{
_objectContextDelegate = objectContextDelegate;
}
public IUnitOfWork Create()
{
ObjectContext context;
lock (_lockObject)
{
context = _objectContextDelegate();
}
return new EFUnitOfWork(context);
}
}
Чтобы использовать это, при запуске приложения я использую structuremap:
ObjectFactory.Initialize(x =>
{
x.For<IUnitOfWorkFactory>().Use<UnitOfWorkFactory>();
x.For(typeof(IRepository<>)).Use(typeof(Repository<>));
});
Комментарии:
1. Как выглядят ваши запросы?
SELECT * FROM LargeTable
?2. Кроме того, как выглядят ваши запросы EF? Вы случайно загружаете много объектов в память, когда вам нужно всего несколько?
3. Вы должны описать, что вы делаете с EF, какой тип приложения вы создаете и как вы работаете с сущностями. В противном случае этот вопрос является кандидатом на закрытие как нереальный вопрос, потому что мы не можем на него ответить.
4. @Ladislav Mrnka: Я думаю, что опубликовать. У меня много кода.. Я полагаю, что запрос select мало что скажет, я что-нибудь опубликую.
5. @Ладислав Мрнка, @Хенк Холтерман, @Джули Лерман: Я добавил больше информации.
Ответ №1:
У меня есть подозрение, что вы не распоряжаетесь контекстом.
Я предлагаю удалять контекст всякий раз, когда вы завершаете взаимодействие с базой данных.
Используйте using
инструкцию всякий раз, когда вы создаете контекст.
[Править]
Насколько я могу видеть, вы кэшируете, а не удаляете свой EFUnitOfWork
объект. Он одноразовый, что правильно, но я не вижу, когда вызывается disposable. Похоже, вы сохраняете ссылку на контекст в течение всего времени выполнения приложения.
Более того, вы создаете и удерживаете по одному контексту на поток, что еще больше ухудшает ситуацию.
Я не могу сказать вам наверняка, куда вы должны поместить Dispose
or using
, поскольку я не знаю обычаев.
Вероятно, вы могли бы использовать это в своем Commit
методе, но я не знаю, вызывался ли Commit
он только один раз во время сеанса взаимодействия с базой данных.
Кроме того, ваш дизайн может быть чрезмерно сложным.
На вашем месте я бы:
- Найдите способ избавиться от контекста, используя текущий код, в качестве краткосрочного решения
- Упростите дизайн, как долгосрочное решение
Если бы у меня было время, я бы сразу же разработал долгосрочное решение.
Но опять же, я не могу сказать, оправдана ли сложность вашего дизайна, поскольку я не знаю, насколько велико ваше приложение, что оно делает и каковы требования.
Комментарии:
1. Хотя это отличный совет для веб-приложений и сервисных операций (и других специфических архитектур) Я бы не стал обязательно предлагать это для каждого сценария (использование и удаление). Это зависит от того, что делает приложение и что создает проблему. Существуют сценарии клиентских приложений, в которых полезно поддерживать контекст (при этом следя за использованием памяти и злоупотреблением кэшем EF). OTOH, если это веб-приложение или служба, .NET будет удалять контекст каждый раз, когда содержащий экземпляр класса выходит за пределы области видимости.
2. @Julie — Я согласен, что в некоторых сценариях было бы предпочтительнее сохранить контекст. Однако я думаю, что в большинстве сценариев он более надежен, масштабируем и менее подвержен ошибкам, которые необходимо утилизировать при первой возможности.
3. @Naor — Из вашего кода неясно, как создается экземпляр EFUnitOfWork.
4. @Alex Aza: Вы правы — я обновил код с фабрикой, которая создает UnitOfWork.
5. @Naor — добавил несколько комментариев к моему ответу.
Ответ №2:
Мне приходит в голову пара вещей:
- Вероятно, вы не удаляете ObjectContext. Убедитесь, что все коды вашей базы данных находятся в
using(var context = CreateObjectContext())
блоке - У вас N-уровневая архитектура, и вы передаете сущности с уровня доступа к данным на верхний уровень, не отсоединяя сущности от ObjectContext. Вам нужно вызвать ObjectContext.Detach(…)
- Скорее всего, вы возвращаете полную коллекцию объектов вместо того, чтобы возвращать один объект для отдельных операций Get. Например, у вас есть запросы типа
from customer in context.Customers select customer
вместо выполненияfrom customer in context.Customers select customer.FirstOrDefault()
Мне было трудно заставить Entity Framework работать в N-уровневом приложении. Он просто не подходит для использования в N-уровневых приложениях как есть. Есть только EF 4.0. Вы можете прочитать обо всех моих приключениях по созданию EF 3 в приложении N-уровня.
http://www.codeproject.com/KB/linq/ef.aspx
Отвечает ли это на ваш вопрос?
Комментарии:
1. @Омар АЛЬ Забир: Откуда вы знаете, что я использую архитектуру N-Tire? какую архитектуру вы рекомендуете?
2. Глядя на ваш код, да, там нет dispose. UnitOfWork создает Objectcontext в текущем потоке, но затем не удаляет его.
3. Обычно я делаю это в своем веб-приложении: при Application_EndRequest я перебираю все элементы в HttpContext. Элементы и посмотрите, какие из них IDisposable, и вызовите их метод Dispose. Затем это очистит всю UnitOfWork. Но еще большая архитектурная проблема заключается в том, что UnitOfWork не должен храниться в контексте. Вы можете создать UnitOfWork где угодно. Это может происходить на бизнес-уровне, возможно, внутри какого-либо метода бизнес-фасада. UnitOfWork используется в блоке using (…), чтобы четко обозначить, где начинается и где заканчивается работа.
4. @Омар АЛЬ Забир: По каким причинам UnitOfWork не следует хранить в контексте?
5. UnitOfWork по замыслу означает небольшую единицу работы, выполняемую в рамках определенного модуля. Один экземпляр UnitOfWork выполняет одно и только одно задание. Когда вы сохраняете его в контексте, вы, по сути, повторно используете один экземпляр UnitOfWork для выполнения нескольких заданий. Это означает, что UnitOfWork становится объектом с отслеживанием состояния и используется для выполнения работы, не связанной с единицами.
Ответ №3:
Вы очищаете ObjectContext
время от времени. Если вы сохраняете ObjectContext
работоспособность в течение длительного времени, это будет потреблять память, связанную с размером EntityDataModel и количеством объектов, загруженных в это ObjectContext
.
Комментарии:
1. @Naor: Как и предполагает Алекс Аза. Я не вижу никакого удаления objectcontext / EFUnitOfwork. @Alex Aza: Я согласен с вами, что дизайн выглядит довольно сложным.
Ответ №4:
У меня была такая же проблема в классе, который использует внедрение зависимостей, поэтому using()
вариант не был альтернативой. Моим решением было добавить DbContextOptions<Context>
в конструктор и в качестве частного поля к классу. Затем вы можете вызвать
_db.Dispose();
_db = new BlockExplorerContext(_dBContextOptions);
в подходящее время. Это устранило мою проблему, когда у меня заканчивалась оперативная память, и приложение было отключено операционной системой.