Оптимальная практика для платформы Entity Framework в бизнес-логике?
Я использую фреймворк Entity в первый раз и хотел бы знать, использую ли я в лучшей практике.
В моей бизнес-логике я создал отдельный класс, который будет обрабатывать контекст сущности. проблема у меня есть, во всех видео, которые я видел, они обычно переносят контекст в оператор using, чтобы убедиться, что он закрыт, но, очевидно, я не могу это сделать в своей бизнес-логике, поскольку контекст будет закрыт, прежде чем я смогу на самом деле используй это?
Так все в порядке, что я делаю? Несколько примеров:
public IEnumerable<Article> GetLatestArticles(bool Authorised)
{
var ctx = new ArticleNetEntities();
return ctx.Articles.Where(x => x.IsApproved == Authorised).OrderBy(x => x.ArticleDate);
}
public IEnumerable<Article> GetArticlesByMember(int MemberId, bool Authorised)
{
var ctx = new ArticleNetEntities();
return ctx.Articles.Where(x => x.MemberID == MemberId && x.IsApproved == Authorised).OrderBy(x => x.ArticleDate);
}
Я просто хочу убедиться, что я не строю то, что собирается умереть, когда многие люди его используют?
Ответы
Ответ 1
На самом деле это зависит от того, как вы хотите открыть хранилище репозитория/данных.
Не уверен, что вы подразумеваете под "контекст будет закрыт, поэтому я не могу вести бизнес-логику". Сделайте свою бизнес-логику внутри оператора using. Или если ваша бизнес-логика находится в другом классе, то пусть продолжит.:)
Некоторые люди возвращают конкретные коллекции из своего репозитория, и в этом случае вы можете обернуть контекст в инструкции using:
public class ArticleRepository
{
public List<Article> GetArticles()
{
List<Article> articles = null;
using (var db = new ArticleNetEntities())
{
articles = db.Articles.Where(something).Take(some).ToList();
}
}
}
Преимущество этого заключается в том, чтобы удовлетвориться хорошей практикой с подключением - открывать как можно скорее и закрывать как можно раньше.
Вы можете инкапсулировать всю свою бизнес-логику в оператор using.
Недостатки - ваш репозиторий становится осведомленным о бизнес-логике, которую мне лично не нравится, и вы получаете другой метод для каждого конкретного сценария.
Параметр второй - новый контекст как часть репозитория и делает его реализующим IDisposable.
public class ArticleRepository : IDisposable
{
ArticleNetEntities db;
public ArticleRepository()
{
db = new ArticleNetEntities();
}
public List<Article> GetArticles()
{
List<Article> articles = null;
db.Articles.Where(something).Take(some).ToList();
}
public void Dispose()
{
db.Dispose();
}
}
И затем:
using (var repository = new ArticleRepository())
{
var articles = repository.GetArticles();
}
Или третий вариант (мой любимый), используйте инъекцию зависимостей. Отмените всю контекстную работу из своего репозитория и дайте контейнеру DI обрабатывать ресурсы:
public class ArticleRepository
{
private IObjectContext _ctx;
public ArticleRepository(IObjectContext ctx)
{
_ctx = ctx;
}
public IQueryable<Article> Find()
{
return _ctx.Articles;
}
}
Ваш выбранный контейнер DI введет конкретный объект ObjectContext в экземпляр Репозитория с настроенным временем жизни (Singleton, HttpContext, ThreadLocal и т.д.) и избавит его от этой конфигурации.
У меня есть настройка, поэтому каждый HTTP-запрос получает новый контекст. Когда запрос будет завершен, мой контейнер DI автоматически удалит контекст.
Я также использую шаблон Unit of Work здесь, чтобы позволить нескольким репозиториям работать с одним Контекстом объектов.
Возможно, вы также заметили, что я предпочитаю возвращать IQueryable из моего репозитория (в отличие от конкретного списка). Гораздо более мощный (но рискованный, если вы не понимаете последствий). Мой уровень сервиса выполняет бизнес-логику в IQueryable, а затем возвращает конкретную коллекцию в пользовательский интерфейс.
Это мой самый мощный вариант, поскольку он позволяет использовать простой репозиторий, а Unit Of Work управляет контекстом, уровень обслуживания управляет бизнес-логикой, а контейнер DI обрабатывает время жизни/распоряжение ресурсами/объектами.
Сообщите мне, хотите ли вы получить дополнительную информацию об этом, поскольку в этом есть много чего, даже больше, чем этот удивительно длинный ответ.:)
Ответ 2
У меня будет ctx как приватная переменная в каждом классе, а затем создайте новый экземпляр этого каждый раз, а затем удалите, когда закончите.
public class ArticleService
{
private ArticleEntities _ctx;
public ArticleService()
{
_ctx = new ArticleEntities();
}
public IEnumerable<Article> GetLatestArticles(bool Authorised)
{
return _ctx.Articles.Where(x => x.IsApproved == Authorised).OrderBy(x => x.ArticleDate);
}
public IEnumerable<Article> GetArticlesByMember(int MemberId, bool Authorised)
{
return _ctx.Articles.Where(x => x.MemberID == MemberId && x.IsApproved == Authorised).OrderBy(x => x.ArticleDate);
}
public void Dispose()
{
_ctx.Dispose();
_ctx = null;
}
}
Затем, когда вы вызываете это.
ArticleService articleService = new ArticleService();
IEnumerable<Article> article = articleService.GetLatestArticles(true);
articleService.Dispose(); // killing the connection
Таким образом вы также можете добавлять/обновлять другие объекты в одном контексте и вызывать метод сохранения, который сохраняет любые изменения в db через Entity.
Ответ 3
По моему опыту этот код не очень хорош, потому что вы теряете способность ориентироваться в отношениях через свойства навигации.
public List<Articles> getArticles( ){
using (var db = new ArticleNetEntities())
{
articles = db.Articles.Where(something).ToList();
}
}
Используя этот подход, вы не можете использовать следующий код, потому что a.Members всегда является нулевым (контекст db близок и не может получить данные автоматически).
var articles = Data.getArticles();
foreach( var a in articles ) {
if( a.Members.any(p=>p.Name=="miki") ) {
...
}
else {
...
}
}
}
Использование только глобального контекста db - плохая идея, потому что вы должны использовать функцию удаления изменений
в пункте вашего приложения, сделайте это, но не сохраняйте изменения и не закрывайте окно
var article= globalcontext.getArticleByID(10);
article.Approved=true;
то в другой точке приложения вы выполните некоторую операцию и сохраните
//..... something
globalcontext.saveChanges();
в этом случае свойство, одобренное предыдущей статьей, настроено на изменение структуры сущности. Когда вы сохраняете, одобренный присваивается значение true!!!
Лучший подход для меня - использование 1 контекста для каждого класса
Вы можете передать контекст другому внешнему методу, если вам нужно
class EditArticle {
private DbEntities de;
private currentAricle;
public EditArticle() {
de = new DbEntities; //inizialize on new istance
}
loadArticleToEdit(Articele a){
// a is from another context
currentArticle= de.Article.Single(p=>p.IdArticle==a.IdArticle){
}
private saveChanges(){
...
pe.saveChanges();
}
}
Ответ 4
То, что вы также можете сделать, это сохранить ваш контекст на более высоком уровне.
Например, у вас может быть статический класс, сохраняющий текущий контекст:
class ContextManager
{
[ThreadStatic]
public static ArticleEntities CurrentContext;
}
Затем, где-то снаружи вы делаете что-то вроде этого:
using (ContextManager.CurrentContext = new ArticleEntities())
{
IEnumerable<Article> article = articleService.GetLatestArticles(true);
}
Затем, внутри GetLastestArticles, вы просто используете тот же ContextManager.CurrentContext.
Конечно, это только основная идея. Вы можете сделать это намного более работоспособным, используя поставщиков услуг, IoC и т.д.
Ответ 5
Вы можете начать подготовку Entity Framework с уровня доступа к данным, создав общий класс репозитория для всех необходимых функций Entity Framework. Затем вы можете использовать его в бизнес-слое (инкапсулированный)
Вот лучшие практики, которые я использовал для Entity Framework в слоях данных, бизнеса и пользовательского интерфейса.
Методы, используемые для этой практики: