Одна библиотека BLL.dll содержит бизнес-логику приложения.
Другая библиотека DAL.dll представляет собой слой доступа к данным.
В библиотеке DAL.dll есть вот такой код (используется Entity Framework — Code First):
namespace Med.Server.DAL
{
public static class Environment
{
internal static readonly DB DB = new DB(); // DB - это наследник DbContextpublic static IQueryable<SheduleUnit> QueryUpdateUnits(DateTime fromDateTime)
{
return from shedule in DB.Shedules
join unit in DB.Units on shedule.Unit.Id equals unit.Id
where fromDateTime > shedule.StartDateTime
select new SheduleUnit { Shedule = shedule, Unit = unit };
}
public static List<string> GetUpdatableUnitsAndUpdateShedule()
{
List<string> units = new List<string>();
DateTime dateTimeNow = DateTime.Now;
var query = QueryUpdateUnits(dateTimeNow);
foreach (var su in query)
{
units.Add(su.Unit.Name);
su.Shedule.StartDateTime = dateTimeNow + TimeSpan.FromSeconds(su.Shedule.Period);
}
if (units.Any())
DB.SaveChanges();
return units;
}
}
}
Правильно ли так будет вообще? Ведь метод GetUpdatableUnitsAndUpdateShedule, вроде как, содержит некую бизнесс-логику, следовательно, ему тут не место?
Может быть будет более правильно сделать класс DB public, и операции в BLL.dll производить непосредственно с ним, избавшись заодно и от класса Environment (это фасад библиотеки DAL) и сделав POCO классы тоже public?
Здравствуйте, Eldar9x, Вы писали:
E>Правильно ли так будет вообще? Ведь метод GetUpdatableUnitsAndUpdateShedule, вроде как, содержит некую бизнесс-логику, следовательно, ему тут не место?
Как вариант — сделать в DAL c репозиторием+спецификации (или QueryObject), а логику вынести туда где им место — в BL.
Основная проблема — это то что ты в dal ввел контекст транзакции. Лучше всего уже на уровне бизнес-сценариев открывать и закрывать контекст с сохранением/ролебэком как только это возможно. Если ты введешь класс контекста транзакции в бизнес логику, тогда все станет проще. А в случае многопоточности, такой статик db вообще не подходит.
Можно сделать класс типа:
public class DbContext:IDisposable
{
DB db;
DAL MyDal{get;set;}
public DbContext(MyDal dal)
{
db=new DB();
CallContext.SetData("DbContext", this);
....
}
public static MyDal{get{ return ((DbContext)CallContext.GetData("DbContext")).MyDal;}}
public static int Commit(){((DbContext)CallContext.GetData("DbContext")).db.SaveChanged();}
public void Dispose(){ db.Dispose(); CallContext.SetData("DbContext", null);}
}
В этом случае использование примерно:
public void bbbInBLL()
{
using (new DBContext())
{
var b=DbContext.MyDal.GetB();
DbContext.MyDal.SetB(b);
DbContext.Commit();
}
}
Здравствуйте, Eldar9x, Вы писали:
E>Одна библиотека BLL.dll содержит бизнес-логику приложения. E>Другая библиотека DAL.dll представляет собой слой доступа к данным. E>В библиотеке DAL.dll есть вот такой код (используется Entity Framework — Code First):
А я давно уже не выделяю DAL. Считаю, что ORM вроде EF вполне достаточный DAL.
Здравствуйте, Doc, Вы писали:
Doc>Как вариант — сделать в DAL c репозиторием+спецификации (или QueryObject), а логику вынести туда где им место — в BL.
Doc, а можете чуть поподробнее, хотя бы небольшие фрагменты кода, как должен выглядеть репозитарий и спецификации в DAL?
Здравствуйте, GlebZ, Вы писали:
Правильно ли я понимаю, что в случае MS SQL Server при каждом вызове будет использоваться пул соединений к базе?
using(DB db = new DB()) // где DB - наследник DbContext из EF
{
// ...
}
В случае MS SQL Server CE приходится каждый раз ждать, пока контекст сформируется, и поэтому я сохраняю DbContext в статик поле, и переиспользую его для каждого вызова. Но обращение происходит из нескольких потоков, как тогда быть? С одной стороны, создание нового контекста на каждый запрос к базе начинает тормозить приложение, а с другой стороны,в случае реюза одного статического DbContext, возникает проблема многопоточности...
Здравствуйте, Ziaw, Вы писали:
Z>А я давно уже не выделяю DAL. Считаю, что ORM вроде EF вполне достаточный DAL.
Ну, не знаю... Мне кажется, мешать в кучу бизнес-логику и объекты БД не очень хорошая идея.
Во-первых, мне не нравится название класса — Environment. У меня классы в DAL называются типа UnitAccessor, ScheduleAccessor (Schedule, кстати, пишется через "c") и т.д.
Во-вторых, да, я склоняюсь что логике из GetUpdatableUnitsAndUpdateShedule() место в слое бизнес-логики.
Чего-то более конкретного подсказать не могу, т.к. с Entity Framework не работал.
У меня методы из бизнес-логики могут выглядеть так:
public void DoSomething()
{
IList<ScheduleUnit> scheduleUnits = ScheduleUnitAccessor.GetUnitsForUpdate(...);
foreach(ScheduleUnit su in scheduleUnits)
{
// do something according to business logic
}
ScheduleAccessor.Save(scheduleUnits);
}
Если вызывается несколько методов из DAL, которые должны выполняться в транзакции, то просто в методе DoSomething() все будет обернуто в using(var transactionScope = new TransactionScope) { }
Здравствуйте, Eldar9x, Вы писали:
Z>>А я давно уже не выделяю DAL. Считаю, что ORM вроде EF вполне достаточный DAL. E>Ну, не знаю... Мне кажется, мешать в кучу бизнес-логику и объекты БД не очень хорошая идея.
Это тема очень холиварная. Но я все-таки за отдельный DAL. Основных причин 2:
1) Повторное использование методов из DAL
2) Структурирование кода/проекта (код начинает выглядеть проще и понятнее, DAL-методы можно отдельно протестировать)
Здравствуйте, Eldar9x, Вы писали:
E>Doc, а можете чуть поподробнее, хотя бы небольшие фрагменты кода, как должен выглядеть репозитарий и спецификации в DAL?
Здравствуйте, MozgC, Вы писали:
MC>Это тема очень холиварная. Но я все-таки за отдельный DAL. Основных причин 2:
"The world is changed. I feel it in the water. I feel it in the earth. I smell it in the air."
(C)
IMHO, когда не было LINQ, и ORM не были так широко распостранены, то выделение отдельного слоя для доступа к данным было оправданно.
Сейчас часть работы "классического" DAL берет на себя ORM (объекты->SQL и обратно), часть — LINQ-провайдер этого же ORM. По сути, от DAL остается только выставленный наружу IQueryable (чтоб можно было написать заглушку для целей тестирования BLL), методы Add/Remove/SaveChanges, да может быть, ряд действительно сложных и действительно переиспользуемых запросов через этот же IQueryable, оформленных в виде отдельных методов.
То, что осталось, конечно, можно называть DAL, только по содержанию это несколько иное.
Наверное, бывают клинические случаи, когда у вас в базе хранятся одни объекты, а бизнес-логика рулит другими объектами, отличающимися настолько, что ORM не сможет это переварить самостоятельно, и вы вынуждены писать мэппинг руками. Но, кмк, таких случаев все-таки меньшинство.
Здравствуйте, Doc, Вы писали:
Doc>Вы POCO c DTO не путаете?
У меня DTO объекты находятся в слое бизнесс-логики. А POKO объекты трансформируются в DTO.
Здравствуйте, Ziaw, Вы писали:
Z>А я давно уже не выделяю DAL. Считаю, что ORM вроде EF вполне достаточный DAL.
Вообщем, я почти к этому и пришел, с небольшим отличием.
Библиотеку DAL оставляем, но держим в ней только голые POKO объекты без какой-либо бизнес-логики.
Все linq запросы, различные операции с POKO объектами выносим в библиотеку BLL.
Есть небольшая проблема в этой схеме. Поначалу я думал, что слой БЛ не должен ничего знать о EF. То есть код БЛ должен обращаться к слою DAL только через операции с обычными CLR типами без обращения к типам EF. Точно так же как библиотека BLL понятия не имеет о том, кто ее будет использовать (у меня это сервис WCF). Поэтому и был сделан класс Environment.
То есть другими словами имеем такую цепочку связей:
WCF->BLL->DAL->EF
Плюс такой схемы в том, что в слой BLL добавляется только ссылка на библотеку DAL, и не добавляются ссылки на нэймспейсы и сборки из EF.
Если же мы экспортируем из DAL POKO объекты EF, позволяя работать с ними напрямую (позволяя использовать тот же linq, например), то приходится ссылки на EF добавлять не только в библиотеку DAL, но и в BLL. Но я с этим смирился.
Здравствуйте, MozgC, Вы писали:
MC>Во-первых, мне не нравится название класса — Environment.
Вот от него я тоже избавился. Экспортируем все POKO объекты наружу и всего делов. И linq запросы в коде бизнесс-логики выглядят совсем даже неплохо
MC>У меня методы из бизнес-логики могут выглядеть так: MC> IList<ScheduleUnit> scheduleUnits = ScheduleUnitAccessor.GetUnitsForUpdate(...);
Название класса ScheduleUnitAccessor уже попахивает бизнесс-логикой
Здравствуйте, Eldar9x, Вы писали:
Doc>>Вы POCO c DTO не путаете? E>У меня DTO объекты находятся в слое бизнесс-логики. А POKO объекты трансформируются в DTO.
Здравствуйте, Eldar9x, Вы писали:
E>В случае MS SQL Server CE приходится каждый раз ждать, пока контекст сформируется, и поэтому я сохраняю DbContext в статик поле, и переиспользую его для каждого вызова. Но обращение происходит из нескольких потоков, как тогда быть? С одной стороны, создание нового контекста на каждый запрос к базе начинает тормозить приложение, а с другой стороны,в случае реюза одного статического DbContext, возникает проблема многопоточности...
1. Пул — это не свойство MSSQL. Это свойство Entities и провайдера. Очень сомневаюсь, что оно вырублено для ce.
2. Думается мне, что формирование коннекта к локальной базе, ни разу не заметно на фоне запросов к данным.
3. В случае многопоточности — да.
Здравствуйте, Doc, Вы писали:
Doc>Тогда вопрос — как вы определяете POCO и DTO?
POKO — обычные классы без родителей, с указанием аттрибутов из DataAnnotations. Находятся в библиотеке DAL.
DTO — классы, с указанием аттрибутов DataContract и DataMember.Находятся в библиотеке WCF сервиса.
Промежуточные классы, находятся в библиотеке BLL. C помощью них происходит трансформация POKO классов в DTO классы.
WCF сервис видит только промежуточные классы и на их основе строит классы DTO, а библиотека BLL видит только классы из DAL.
Получается тройное дублирование, но я просто я не знаю, как по другому разделить слои в соответствии с цепочкой WCF->BLL->DAL...
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, Eldar9x, Вы писали:
E>>В случае MS SQL Server CE приходится каждый раз ждать, пока контекст сформируется, и поэтому я сохраняю DbContext в статик поле, и переиспользую его для каждого вызова. Но обращение происходит из нескольких потоков, как тогда быть? С одной стороны, создание нового контекста на каждый запрос к базе начинает тормозить приложение, а с другой стороны,в случае реюза одного статического DbContext, возникает проблема многопоточности... GZ>1. Пул — это не свойство MSSQL. Это свойство Entities и провайдера. Очень сомневаюсь, что оно вырублено для ce. GZ>2. Думается мне, что формирование коннекта к локальной базе, ни разу не заметно на фоне запросов к данным. GZ>3. В случае многопоточности — да.
Как оказалось, во всяком случае, для MS SQL Server, тормозит самая первая выборка, это что-то связанное с начальной загрузкой модели базы данных в память EF. Другими словами, необходимо либо "прогревать" EF перед использованием, либо смириться с тем, что первый вызов будет медленный. Таким образом, для MS SQL Server я использую создание отдельного контекста на каждое обращение к БД. Насчет MS SQL CE — не проверял, скорее всего, причина в том же. У меня сейчас архитектура приложения немного изменилась, я просто скачиваю все настройки в память клиента с сервера, поэтому, скорее всего, локальная база на клиенте и не понадобится.
Здравствуйте, GlebZ, Вы писали:
GZ>1. Пул — это не свойство MSSQL. Это свойство Entities и провайдера. Очень сомневаюсь, что оно вырублено для ce.
Зря сомневаетесь
Здравствуйте, Eldar9x, Вы писали:
E>Как оказалось, во всяком случае, для MS SQL Server, тормозит самая первая выборка E>Другими словами, необходимо либо "прогревать" EF перед использованием, либо смириться с тем, что первый вызов будет медленный.
В случае использования EF, это характерно для любого провайдера.
Существуют способы это оптимизировать, если торможение критично:
для "классического" EF (database/model first)
для code first: вариант 1, вариант 2.
Ну и, конечно, не нужно создавать, модели из нескольких десятков сущностей и более — ваш сервис, который будет работать с моделью EF, не должен отвечать за все.
E>Насчет MS SQL CE — не проверял, скорее всего, причина в том же.
Немножко не так. Для CE, помимо тормозов при создании модели EF, накладываются тормоза, связанные с тем, что сам движок CE выгружается из памяти при закрытии последнего соединения. Т.о., если вы не держите где-то соединение-пустышку (статический экземпляр, не используемый нигде в вашем приложении), то вы при каждом поключении загружаете CE заново.
Здравствуйте, Eldar9x, Вы писали:
E>Одна библиотека BLL.dll содержит бизнес-логику приложения. E>Другая библиотека DAL.dll представляет собой слой доступа к данным. E>В библиотеке DAL.dll есть вот такой код (используется Entity Framework — Code First):
E>
E>namespace Med.Server.DAL
E>{
E> public static class Environment
E> {
E> internal static readonly DB DB = new DB(); // DB - это наследник DbContext
E> public static IQueryable<SheduleUnit> QueryUpdateUnits(DateTime fromDateTime)
E> {
E> return from shedule in DB.Shedules
E> join unit in DB.Units on shedule.Unit.Id equals unit.Id
E> where fromDateTime > shedule.StartDateTime
E> select new SheduleUnit { Shedule = shedule, Unit = unit };
E> }
E> public static List<string> GetUpdatableUnitsAndUpdateShedule()
E> {
E> List<string> units = new List<string>();
E> DateTime dateTimeNow = DateTime.Now;
E> var query = QueryUpdateUnits(dateTimeNow);
E> foreach (var su in query)
E> {
E> units.Add(su.Unit.Name);
E> su.Shedule.StartDateTime = dateTimeNow + TimeSpan.FromSeconds(su.Shedule.Period);
E> }
E> if (units.Any())
E> DB.SaveChanges();
E> return units;
E> }
E> }
E>}
E>
E>Правильно ли так будет вообще? Ведь метод GetUpdatableUnitsAndUpdateShedule, вроде как, содержит некую бизнесс-логику, следовательно, ему тут не место? E>Может быть будет более правильно сделать класс DB public, и операции в BLL.dll производить непосредственно с ним, избавшись заодно и от класса Environment (это фасад библиотеки DAL) и сделав POCO классы тоже public?
E>POKO — обычные классы без родителей, с указанием аттрибутов из DataAnnotations. Находятся в библиотеке DAL. E>DTO — классы, с указанием аттрибутов DataContract и DataMember.Находятся в библиотеке WCF сервиса. E>Промежуточные классы, находятся в библиотеке BLL. C помощью них происходит трансформация POKO классов в DTO классы. E>WCF сервис видит только промежуточные классы и на их основе строит классы DTO, а библиотека BLL видит только классы из DAL. E>Получается тройное дублирование, но я просто я не знаю, как по другому разделить слои в соответствии с цепочкой WCF->BLL->DAL...
Вот тут спорят за POCO vs DTO. Вкратце, POCO — обычные классы некоторого языка (C#) с пропертями, и, возможно, с некоторыми поведением,
валидацией, например, или какой-либо аггрегирующей функцией. А DTO вообще опрделяют как паттерн...
Кстати, у меня DTO объекты находятся в DAL, т.к. многие методы в DAL уже возвращают DTO объекты,т.е. готовые к отрпавлению через WCF сервис. Хотя, возможно, это и не правильно.
Надо будет поправить.
Здравствуйте, Sharov, Вы писали:
S>Вот тут спорят за POCO vs DTO. Вкратце, POCO — обычные классы некоторого языка (C#) с пропертями, и, возможно, с некоторыми поведением, S>валидацией, например, или какой-либо аггрегирующей функцией. А DTO вообще опрделяют как паттерн... S>Кстати, у меня DTO объекты находятся в DAL, т.к. многие методы в DAL уже возвращают DTO объекты,т.е. готовые к отрпавлению через WCF сервис. Хотя, возможно, это и не правильно. S>Надо будет поправить.
У вас WCF сервис встроен в библиотеку с BL, то есть вы не создавали отдельную специальную библиотеку для сетевого слоя?
Здравствуйте, Eldar9x, Вы писали:
E>Как оказалось, во всяком случае, для MS SQL Server, тормозит самая первая выборка, это что-то связанное с начальной загрузкой модели базы данных в память EF. Другими словами, необходимо либо "прогревать" EF перед использованием, либо смириться с тем, что первый вызов будет медленный.
По любому первый вызов тормозит. Но:
Здравствуйте, Eldar9x, Вы писали:
E>У вас WCF сервис встроен в библиотеку с BL, то есть вы не создавали отдельную специальную библиотеку для сетевого слоя?
Не очень понял Ваш вопрос. У меня в бизнес логике приблизительно такие методы:
[ServiceBehavior(Namespace = "http://ns", InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public sealed partial class SomeManager : IManager, IDisposable
{
...
public IEnumerable<UserDto> Users()
{... }
...
}
То есть данные от BL уровня, как я его понимаю, сразу отправляются в сеть.
Т.е. если таки я правильно понял Ваш вопрос, сервисный уровень у меня встроен в BL.