-->

пятница, 27 мая 2016 г.

Кэширование в Entity Framework

Встроенные возможности

Как многим давно известно из этой замечательной статьи, в средствами Entity Framework кэшируются следующие объекты:
  1. Планы выполнения запросов; 
  2. Метаданные;
  3. Скомпилированные запросы.
Можно ли контролировать этот кэш? Увы, кэширование планов можно отключить при помощи ObjectQuery.EnablePlanCaching, а более тонкие настройки (размер хранилища, expiration policy) - недоступны.
Есть в EF и кэш данных, известный многим: в пределах одного DbContext с доступом через метод Find, что весьма ограничивает возможности его применения. Да и управлять этим кэшем напрямую - по-прежнему невозможно.

Сторонний кэш данных

К счастью в Entity Framework есть возможность использования стороннего кэша для полученных из БД данных (second level cache). И как мы видим из этой статьи, подключить кэш данных не так уж и сложно. А еще проще использовать уже существующий проект, коих немало:

EntityFramework-Plus
NCache
EntityFramework.Cache

Есть, из чего выбрать!

понедельник, 22 февраля 2016 г.

Тестирование WCF через SoapUI

Обращение через SoapUI к веб-сервису WCF с привязкой WSHttpBinding и авторизацией оказалось не самой очевидной задачей. Вот привязка:
<wsHttpBinding>
   <binding name="FooBinding" >
      <security mode="TransportWithMessageCredential">
         <transport clientCredentialType="None"> </transport>
         <message clientCredentialType="UserName"
                        algorithmSuite="Default"
                        negotiateServiceCredential="false"
                        establishSecurityContext="false"/>
      </security>
   </binding>
</wsHttpBinding>

Прежде всего создаем SOAP-проект и скармливаем ему WSDL. Открываем созданный SOAP Request для нужного метода. Добавляем авторизацию HTTP Basic:

 Там же включаем WS-A Adressing:


В свойствах запроса устанавливаем WSS-Password Type: Password Text:


А в настройках SoapUI (Tools -> Preferences -> HTTP Settings) ставим
Authenticate Preemptively: true.


Этого должно быть достаточно, чтобы обращение к веб-сервису было успешным.

Полезные советы:
1. В SoapUI любой элемент от реквеста до проекта можно склонировать.

2. Для задания логина/пароля и любых других свойств на уровне проекта достаточно создать для проекта Custom Property, к примеру PASSWORD. Обращение к свойству:
${#Project#PASSWORD}
Вбиваем в поле Password вкладки Auth, и готово!

3. Формат проекта SoapUI - обычный XML-файл. К примеру тот же самый пароль сохраняется в проекте как plain text, пригодный для редактирования через Find/Replace. Только не забудьте после сохранения файла во внешнем редакторе перезагрузить проект в SoapUI через клавишу F5.



понедельник, 15 февраля 2016 г.

Снятие бэкапа TeamCity через PowerShell

Задача: выполнять регулярный бэкап билд-конфигураций TeamCity, получать и хранить файл с бэкапом.

Простейший способ получить резервную копию содержимого сервера TeamCity - это зайти в админке в Administration - Backup, указать, что мы собираемся копировать и нажать Start Backup. В зависимости от количества билд-конфигураций, упаковка файла может занять несколько минут, а результат работы будет сохранен в указанную папку на сервере TeamCity.
К сожалению, этот билд-сервер не предоставляет "из коробки" функциональности по автоматизации снятия бэкапа. Поискав в сети, конечно же я наткнулся на широко известный PowerShell-скрипт от Ivan Leonenko, позволяющий запустить бэкапирование удаленно посредством отправки HTTP POST-запроса.

Вместо того, чтобы складывать бэкапы на той же машине, где развернут TeamCity (что совершенно неправильно) и писать службу по их миграции в надежное хранилище, мы решили сразу получать файл в свое распоряжение. Для получения файла достаточно GET-ом с той же авторизацией обратиться по адресу:
{server}/get/file/backup/{filename}
поэтому наша доработка скрипта Ivan'a свелась к периодическому опросу этого URL и скачиванию файла (как подписаться на событие "бэкап готов" даже смотреть не стали).

Готовый скрипт на GitHub. Остается настроить периодический запуск скрипта через любой планировщик задач, и файлы с бэкапами начнут поступать в ваше распоряжение. Если собираетесь делать через SQL Server Agent - прочитайте предыдущую заметку, поскольку у агента свои причуды выполнения PowerShell-скриптов.

Кстати, скачать файл через PowerShell несложно, достаточно использовать командлет Invoke-WebRequest:

function Save-FileFromWeb()
{
param(
[string] $url,
[string] $username,
[string] $password,
[string] $targetFile
)
$authInfo = $username + ":" + $password
$authInfo = [System.Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($authInfo))
$Headers = @{ Authorization = "Basic $authInfo" }
Invoke-WebRequest -Uri $url -Headers $Headers -OutFile $targetFile
}

понедельник, 8 февраля 2016 г.

Бэкап БД SQL Server по-расписанию через PowerShell и SQL Server Agent job

Задача: автоматизировать снятие BACPAC пакета с базы данных SQL Server (для Azure SQL Database тоже работает). Напомню, BACPAC не содержит transaction log, соответственно для point-in-time restore не подойдет. В нашем случае нужен для быстрого развертывания "по требованию" копии базы с нужными данными, для проведения интеграционного тестирования.

Идеальным решением оказался скрипт на PowerShell. Код простейший, я взял гуляющую по интернету версию (к сожалению, ссылка на исходный скрипт затерялась) и немного ее доработал в нужную сторону. Создание бэкапа выполняется следующими командлетами:

$Services = new-object Microsoft.SqlServer.Dac.DacServices $ConnectionString
$Services.ExportBacpac($OutputFile, $DatabaseName)

Есть полный работоспособный пример на GitHub.  Для работы необходим PowerShell 3.0+ и Microsoft® SQL Server® Data-Tier Application Framework, желательно последней версии. На х64 машине необходимо установить как х86, так и х64 версию данной библиотеки.

Куда больше времени пришлось потратить на запуск скрипта через SQL Server Agent, когда я решил автоматизировать создание пакета с помощью периодически выполняемой задачи (job). Как оказалось, SQL Server  может использовать различные версии powershell для выполнения скрипта, в зависимости от версии самого SQL Server и step type у нашей задачи. Вообще говоря, выполнить PowerShell от лица SQL Server Agent возможно из нескольких типов шагов:
  • Operating System (CmdExec)
  • Powershell
Вот здесь есть таблица зависимости версий. Например, SQL Server 2012 с типом шага "Powershell" будут использовать PowerShell версии 2.0, что приведет к ошибке выполнения скрипта (нужен 3.0+). Независимо от наличия более свежих версий PS.
Сразу приведу workaround: для успешного выполнения Powershell скрипта через агента SQL Server 2012-2014 необходим шаг Operating System (CmdExec) со следующей командой:

powershell.exe -version 3.0 -ExecutionPolicy Bypass -file "C:\yourscript.ps1"

вторник, 2 февраля 2016 г.

Подменяем DbContext, изолируем базу данных для тестирования

Многие, использующие Entity Framework сталкиваются с проблемой изоляции внешних зависимостей (в данном случае БД) при написании юнит-тестов. В этом примере я покажу, как подменить DbContext прозрачно для вызывающего кода, и заполнить поддельный DbSet тестовыми данными. Такой прием полезен для тестирования кода доступа к данным в изоляции от БД. Актуально для Entity Framework 6.

Мы будем тестировать популярный паттерн: некий класс-"репозиторий", инкапсулирующий в себе запрос к DbSet, транслируемый в запрос к базе данных:
public class ReportRepository
{
 [Inject]
 public IContextFactory ContextFactory { getset; }
 
 public List<Report> GetReports()
 {
  using (var context = new FooContext())
  {
   return context.Reports.ToList();
  }
 }
}
Этот код не назовешь слабосвязанным, так как присутствует зависимость от конкретной реализации контекста - класса FooContext. Нам необходимо в первую очередь избавиться от этой зависимости. Для этого модифицируем наш контекст, выделив необходимые методы и свойства в интерфейс (например, путем модификации шаблонов T4, генерирующих контекст):
public partial class FooContext : DbContextIFooContext
{
 public IDbSet<Report> Reports { getset; }
}
Однако, класс-репозиторий сам управляет временем жизни контекста, поэтому прямое внедрение интерфейса не подходит. Вместо этого, используем паттерн "абстрактная фабрика" (все подробности - в известной книге "Dependency Injection on .NET", сейчас практически стандарту по DI в дотнете). Итак, наша фабрика будет создавать новые экземпляры контекста:
public class ContextFactory : IContextFactory
{
 public IFooContext Create()
 {
  return new FooContext();
 } 
}
Внедрим фабрику в "репозиторий":
public class ReportRepository
{
 [Inject]
 public IContextFactory ContextFactory { getset; }
 
 public List<Report> GetReports()
 {
  using (var context = ContextFactory.Create())
  {
   return context.Reports.ToList();
  }
 }
}
Теперь использующий контекст не знает ничего о конкретной реализации контекста. Осталось только сконфигурировать наш DI контейнер (здесь и далее используется Ninject):
public class AppModule : NinjectModule
{
 public override void Load()
 {
  Bind<IContextFactory>().To<ContextFactory>();
 }
}
Самое время выполнить интеграционный тест, запрашивающий данные из реальной БД:
[Test]
public void RealRepository_WhenCalled_ReturnsEmpty()
{
 var kernel = new StandardKernel();
 kernel.Load<AppModule>();
 
 var repo = kernel.Get<ReportRepository>();
 
 var reports = repo.GetReports();
 
 Assert.IsEmpty(reports);
}

Переходим непосредственно к подмене контекста. Для этого нам понадобится еще один модуль Ninject, который достаточно создать в проекте с изолированными от БД тестами:
public class MockModule : NinjectModule
{
 public override void Load()
 {
  Rebind<IContextFactory>().To<MockContextFactory>();
 }
}
Для создания поддельных объектов используется фреймворк Moq. Обратите внимание на реализацию MockContextFactory: вся хитрость - в методе MockDbSet<T>, который создает мок-объект, реализующий IQueryable<T>:
public class MockContextFactory : IContextFactory
{
 public IFooContext Create()
 {
  var mockRepository = new MockRepository(MockBehavior.Default);
 
  var mockContext = mockRepository.Create<IFooContext>();
 
  mockContext.Setup(x => x.SaveChanges())
   .Returns(int.MaxValue);
 
  List<Report> mockReports = MockReports();
 
  var mockDbSet = MockDbSet<Report>(mockReports);
 
  mockContext.Setup(m => m.Reports).Returns(mockDbSet.Object);
 
  return mockContext.Object;
 }
 
 private List<Report> MockReports()
 {
  List<Report> mockReports = new List<Report>();
 
  mockReports.Add(new Report {Id = 1, Name = "Mock Report #1"});
 
  return mockReports;
 }
 
 private Mock<DbSet<T>> MockDbSet<T>(List<T> data = nullwhere T : class
 {
  if (data == null) data = new List<T>();
 
  var queryable = data.AsQueryable();
 
  var mock = new Mock<DbSet<T>>();
 
  mock.As<IQueryable<T>>().Setup(m => m.Provider)
   .Returns(queryable.Provider);
  mock.As<IQueryable<T>>().Setup(m => m.Expression)
   .Returns(queryable.Expression);
  mock.As<IQueryable<T>>().Setup(m => m.ElementType)
   .Returns(queryable.ElementType);
  mock.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
   .Returns(queryable.GetEnumerator());
 
  return mock;
 }
}
Этого достаточно! Мы можем обращаться к поддельному DbSet, получать тестовые данные через LINQ, при этом подмена контекста останется незамеченной для использующего контекст кода. Для проверки напишем еще один тест:
[Test]
public void RealRepository_WhenCalled_ReturnsMockData()
{
 var kernel = new StandardKernel();
 kernel.Load<AppModule>();
 //Order is important. In MockModule we rebind IContextFactory.
 //Another option is to load MockModule only.
 kernel.Load<MockModule>(); 
 
 var repo = kernel.Get<ReportRepository>();
 
 var reports = repo.GetReports();
 
 Assert.IsNotEmpty(reports);
}

Данный способ - не 100% пацея и побочные эффекты могут возникнуть, поскольку в LINQ-запросе к DbSet будет использоваться LINQ to Objects, а не LINQ to Entities.
Полный пример для EF6 + SQL Server доступен на GitHub.

понедельник, 1 февраля 2016 г.

Логируем метод вместе с аргументами при помощи аспектов и DI

Очередной совет из серии "хорошо за 5 минут" - логируем вызов метода вместе со списком и значениями входных параметров!

Пусть будет для примера старинный ASMX клиент:
public partial class LegacyClient : System.ServiceModel.ClientBase<Legacy>, Legacy
у которого большое количество методов, переплетенных в причудливые макаронины вместе с остальным кодом. Писать свою обертку долго и больно, а логировать вызовы надо. Будем делать на аспектах с помощью DI-контейнера! В данном примере - Ninject. Конкретный контейнер не так важен. Важно, поддерживает ли он "перехватчики" - interceptors. На том, что такое Dependency Injection и DI-контейнеры - останавливаться не буду, все уже жевано-пережевано до меня.

Поехали, выделяем интерфейс с нужными методами (R# спешит на помощь):
public partial class LegacyClient : ILegacyClient {}
В конфиге контейнера указываем наш interceptor:
Kernel.Bind(typeof(ILegacyClient))
    .To<LegacyClient>()
    .Intercept()
    .With<LogRequestInterceptor>();
А вот и реализация interceptor'а:
using Ninject;
using Ninject.Extensions.Interception;

public class LogRequestInterceptor : IInterceptor 
{
    public void Intercept(IInvocation invocation)
    {
        MethodInfo method = invocation.Request.Method;

        var parameters = method.GetParameters();

        var builder = new StringBuilder();

        for (int index = 0; index < parameters.Length; index++)
        {
            object argument = invocation.Request.Arguments[index];

            ParameterInfo parameterInfo = parameters[index];

            if (!parameterInfo.IsOut)
            {
                //use any serialization you like
                string text = $"{parameterInfo.Name} = {argument.ToJson()}, ";

                builder.Append(text);
            }
        }

        string joinedParameters = builder.ToString();

        YourLogging(method.Name, joinedParameters); 

        //LegacyClient method call
        invocation.Proceed();
    }
}
Дополнительно к нашему "перехватчику" необходимо реализовать сериализацию аргументов метода (я предпочитаю JSON).

Получение объекта с дополнительными аспектами ничем не отличается от обычного resolve'а с помощью DI:
ILegacyClient client = Kernel.GetService<ILegacyClient>();
client.AnyMethodCall(...);
Самое важное в этом примере - мы не меняем интерфейс класса LegacyClient, поэтому вызывающий методы данного класса код остается нетронутым, и измениться не может в принципе. Необходимо лишь изменить способ создания объекта, что зачастую является куда менее трудоемкой задачей, чем написание прокси-класса. На этом всё, побольше удачи и поменьше рутины!




понедельник, 25 января 2016 г.

Пишем простейший плагин для ReSharper

Многие знают, что ReSharper, помимо собственной логики, предоставляет возможности для ее дальнейшего расширения с помощью плагинов. Разработка расширений осуществляется с помощью ReSharper SDK - официального набора инструментов и библиотек от JetBrains. Довелось мне использовать данную технологию, и было это непросто, скажу я вам. Несмотря на высокое качество официальной документации, она все же не в состоянии объять необъятное. API библиотек зачастую неочевиден, а гайдов и мануалов от сторонних исследователей в сети очень мало, да и те устаревают с выходом каждой следующей мажорной версии. Искать нужную информацию на русском языке практически бесполезно, по моему опыту. Плагинов написано немного, и всенародной популярностью вряд ли пользуется хоть один - наверняка не последнюю роль в этом сыграл огромный и самодостаточный набор фич самого ReSharper, да еще и растущий с каждым релизом.

В итоге, когда я решил поделиться накопленными знаниями с общественностью, материала с лихвой хватило для размещения на habrahabr.ru:


На данный момент это единственный известный мне актуальный материал на русском языке (да и на английском тоже - остальное устарело), освещающий полный цикл разработки и тестирования простейшего плагина-расширения ReSharper.

Краткое содержание:
  1. Настройка среды разработки
  2. Пример №1: простейшее расширение-заглушка
  3. Установка плагина
  4. Отладка, полезные советы
  5. Пример №2: модификация кода с помощью R# API
  6. Функциональное тестирование плагинов средствами R# API
Выстраиваемое ReSharper синтаксическое дерево - уникальная технология, позволяющая творить невероятные вещи с кодом. Желаю этому продукту дальнейшего развития, больше полезных плагинов, и конечно же - больше интересных публикаций!