Разделение структуры LINQ to SQL - Использование кода
ОГЛАВЛЕНИЕ
Использование кода
Реализация сервисов данных
Обычно при использовании LINQ to SQL вам необходимо писать запросы, основанные на таблице свойств, сгенерированной по DataContext, к примеру :
var article = myDataContext.Articles.Where(x => x.ArticleId == 1).SingleOrDefault();
или:
var article = myDataContext.GetTable<Article>().
Where(x => x.ArticleId == 1).SingleOrDefault()
Это не может быть выполнено с данной структурой поскольку ни Article, ни GetTable<T> не являются членами IDataContext. Вместо этого нам необходимо использовать специализированный метод GetITable<T> , который был создан для того, чтобы показать объект IEnumerable<T> запросу :
var article = myDataContext.GetITable<Article>().
Where(x => x.ArticleId == 1).SingleOrDefault()
При наличии указанного выше синтаксиса наши методы сервисов данных могут выглядеть следующим образом:
public List<IMember> GetMemberStartingWith(char c)
{
return (from m in this.Factory.DataContext.GetITable<Member>()
where m.Name.StartsWith(c.ToString())
select (IMember)m)
.ToList();
}
Как оговаривалось в начале статьи, один из недостатков данного подхода заключается в том, что мы не запрашиваем напрямую по System.Data.Linq.Table<T>, поэтому мы теряем возможность использовать дополнительные методы расширения, доступные объекту System.Data.Linq.Table<T> по сравнению с объектом IEnumerable<T>.
Экспозиция сервисов данных
EntityServiceFactory включает в себя основные методы создания сервисов и сущностей со всеми привязанностями уже установленными. Но, тем не менее, более элегантная реализация заключается в расширении данного класса и экспозиции свойств для получения доступа к каждому сервису данных. В данном примере этот класс называется ServiceFactory и он достаточно прост, обладая свойствами: CommentService, ArticleService и MemberService. Каждый вызов к любому из этих свойств возвратит новый объект сервиса, созданный из Dependency Injection (внедрения зависимости). В наиболее простой форме одно из свойств может выглядеть так:
public IArticleService ArticleService
{
get
{
return this.GetService<Article, IArticleService>();
}
}
Внедрение политики (Policy Injection)
Внедрение политики (Policy Injection) - это простой тип AOP- структуры (автономной платформы объектов) , которую можно найти в библиотеке Microsoft Enterprise Library. В данном примере мы будем использовать внедрение политики (Policy Injection) для выполнения регистрации и кэширования на уровне метода путем простого добавления атрибутов к методам, которые необходимо кэшировать или регистрировать. Для реализации внедрения политики мы изменим код указанных выше свойств на:
public IArticleService ArticleService
{
get
{
IArticleService service = this.GetService<Article, IArticleService>();
return PolicyInjection.Wrap<IArticleService>(service);
}
}
Внедрение политики (Policy Injection) требует от объекта расширения MarshalByRefObject или чтобы оно реализовало интерфейс, содержащий методы, которые будут использованы во внедрении политики. Поскольку все наши классы имеют интерфейсы, это будет легко.
Для того, чтобы кэшировать выходной результат метода, вам понадобится добавить CachingCallHandler:
[CachingCallHandler(0, 5, 0)]
public new List<IComment> SelectAll()
{
return base.SelectAll()
.Cast<IComment>()
.ToList();
}
Теперь выходной результат SelectAll() будет кэширован на 5 минут. Регистрация настолько же легка, тем не менее она требует некоторые записи в файле конфигурации:
[LogCallHandler(BeforeMessage = "Begin", AfterMessage = "End")]
public IMember CreateNew(string name, string email, string phone)
{
Member member = this.Factory.CreateEntity<Member>();
member.Name = name;
member.Email = email;
member.Phone = phone;
member.DateCreated = DateTime.Now;
return (IMember)member;
}
Указанный код создаст запись регистрации до того как будет вызван метод с переданными значениями параметров, и после данный метод будет вызван со значением возвращенного объекта. Секция конфигурации в блоке регистрирующего приложения позволит вам настроить именно то, что будет записано и как оно будет отформатировано.
Добавление атрибутов несложно, и вы также можете настроить внедрение политики (Policy Injection) в конфигурационном файле так, чтобы он динамически изменял то, что кэшируется, записывается и т.д., без необходимости в повторной компиляции. Тем не менее, целевые методы должны существовать внутри объекта, который упакован или создан при помощи внедрения политики (Policy Injection).
Использование сервисов данных
Все, что вам необходимо сделать, это использовать сервисы данных для создания ServiceFactory и получения доступа к свойствам для вызова соответствующих методов. Это создаст новый IMember:
ServiceFactory factory = new ServiceFactory();
IMember newMember = factory.MemberService
.CreateNew("Shannon", "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.", "12345676");
За занавесом будет создан новый объект Member, а также будет вызван метод InsertOnSubmit его соответствующего члена ITable. Для того, чтобы сохранить изменения DataContext, мы можем просто вызвать:
newMember.Save();
Вызов factory.DataContext.SubmitChanges() выполнит то же самое (но, вышеуказанное кажется более элегантным), а LINQ to SQL не предлагает красивого способа выполнить обновление сущности или таблицы, оно просто обновляет все выполненные изменения, поэтому метод Save() на самом деле просто оболочка для DataContext.SubmitChanges().
Поскольку мы объявили IDataContext в качестве одноэлементного множества, это означает, что нам не нужно заботиться о том, какой DataContext создал ту или иную сущность, поскольку он всегда будет одним и тем же после выделения. Это позволит нам создать различные сущности для различных сервисов, связать их вместе, сохранить изменения в базе данных и не заботиться об ошибках относительно несогласованных DataContext:
IMember newMember = memberService.CreateNew("My Name",
"Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.", "00000000");
IArticle newArticle = articleService.CreateNew("My Article",
"Some text...", "Some description...");
//создание 20-и новых комментариев при помощи созданных выше IMember и IArticle
List<IComment> comments = new List<IComment>();
for (int i = 0; i < 20; i++)
comments.Add(commentService.CreateNew("My Comment", newMember, newArticle));
//одновременное сохранение всех новых комментариев в базе данных
factory.DataContext.SubmitChanges();
Использование внедрения зависимости (Dependency Injection) для связывания с альтернативными контекстами данных
Как уже отмечалось в начале статьи, мы создали тестовый контекст данных названный XDataContext , который хранит данные в XML-файлах вместо базы данных. Мы определили второй контейнер в файле конфигурации, который является таким же как и SQL-контейнер. Тем не менее, IDataContext связан с этим XDataContext вместо того, чтобы быть связанным с DataContext от LINQ to SQL . Мы не создавали специализированные сущности поскольку сущности LINQ to SQL не так сложно создать и они уже обрабатывают отношения между сущностями.
Для того, чтобы использовать другие контейнеры, нам необходимо выстроить EntityServiceFactory с названием контейнера.
XDataContext управляет установкой идентификации и приращением, а также отслеживанием дополнений или удаления.
Пункты повышенного интереса
Полезные методы, такие как Delete() и Save() , которые теперь существуют в этих сущностях, имеют некий подвох. Использование метода EntityServiceFactory's CreateEntity<T> для создания сущности автоматически привязывает зависимости сущности с IDataContext, тем самым Save() и Delete() могут быть вызваны. Тем не менее, когда данные сущности будут возвращены из источника данных, они не будут обладать установленными зависимостями. Для того, чтобы это было иначе, необходимо использовать метод BuildEntity<T> из EntityServiceFactory для связи зависимостей с каждым объектом. Это, скорее всего, привнесет некоторое снижение производительности. К примеру, метод SelectAll():
public virtual List<T> SelectAll()
{
List<T> entities = (from x in Factory.DataContext.GetITable<T>() select x).ToList();
entities.ForEach(x => Factory.BuildEntity<T>(x));
return entities;
}
вызывает BuildEntity для каждого элемента, возвращенного из источника данных. Представьте себе сотни тысяч кортежей - в этом случае цена будет гораздо больше. Тем не менее, несмотря на снижение производительности со стороны BuildEntity, оно не настолько значительно по сравнению с использованием стандартного LINQ to SQL со множеством итераций.
С другой стороны, в некоторых источниках указывается , что сериализация сущностей LINQ to SQL к XML невозможна без хитростей, потому для данного примера мы просто реализовали IXmlSerializable и специализировано упорядочили данные объекты.
Вывод
Мы продемонстрировали вам некоторые передовые методы. Было интересно пробовать обойти структуру классов LINQ to SQL для реализации макета сервисов без использования некоторого рода библиотеки макетов.
Рекомендуется скачать исходный код и разобраться в том, что на самом деле происходит.
Shannon Deminick