Я работала со многими клиентами, помогая им в рефакторинге существующего ПО, которое использует Entity Framework (EF). В случае EF-кода смысл рефакторинга может быть гораздо обширнее и зачастую включать обновление. В этой статье я рассмотрю некоторые способы, которыми вы можете переработать свой EF-код или приложения, использующие EF. Для каждого из этих подходов я дам ряд рекомендаций на основе собственного опыта в работе над производственными приложениями клиентов, что должно помочь вам хорошо подготовиться и избежать определенных проблем.
Изменения, которые я буду обсуждать:
- обновление до новой версии Entity Framework;
- разбиение большой Entity Data Model;
- замену ObjectContext API на DbContext API.
В этой статье я расскажу о первых двух сценариях, не вдаваясь в детали, которые мы отложим до следующей статьи; в ней же будет рассмотрен и третий сценарий, а также будут даны конкретные примеры.
Прежде чем приступать к любой из этих задач, настоятельно советую последовать главному правилу: занимайтесь ими по одной за раз. У меня уже есть опыт участия в амбициозном начинании переделать старый проект, который использовал EF4, огромную модель и ObjectContext API. Попытка заменить сразу три части может привести только к разочарованию, если не к чему-нибудь похуже. В данном случае я предложила сначала обновить версию EF без внесения каких-либо иных изменений, а затем убедиться, что все работает. На следующем этапе следовало определить область модели, которую можно было бы выделить в новую небольшую модель. Но эта новая модель по-прежнему должна использовать ObjectContext API. Когда все было вновь возвращено в рабочее состояние, мы начали переход на DbContext API, но только для этой малой модели. В ином случае слишком многое было бы разрушено в рамках приложения и пришлось бы долго мучиться, пытаясь выловить многочисленные «баги». Разбив крупную модель на несколько малых и занимаясь только одной моделью единовременно, вы имеете дело с куда меньшим объемом разрушенного кода, который подлежит переработке; кроме того, вы извлекаете хорошие уроки и находите шаблоны, которые позволят переработать следующую малую модель с куда меньшими проблемами.
Обновление до более новой версии Entity Framework
Благодаря тому, что группа EF много внимания уделяет обратной совместимости, переход с одной версии на другую осуществляется с минимальными издержками. Я сосредоточусь на обновлении до EF6 как основной версии, но также поговорю и о дополнительных версиях вроде EF6.02 и EF6.1.
Проблемы при переходе с EF4, EF4.1 или EF5 на EF6 (впрочем, по моему мнению, на самом деле они не столь серьезны) возникают из изменений некоторых пространств имен. Поскольку оригинальные API по-прежнему находятся в Microsoft .NET Framework, их дублирование в EF6 могло бы создать трудности. Поэтому в EF6 API эти классы выделены в пространство имен, отличное от System.Data; это позволяет избегать конфликтов. Например, в EF API на основе .NET есть ряд пространств имен, которые начинаются с System.Data.Objects, System.Data.Common, System.Data.Mapping, System.Data.Query и некоторых других. Также имеются классы и перечисления, содержащиеся непосредственно в System.Data, например System.Data.EntityException и System.Data.EntityState. Большинство классов и пространств имен, которое было прямо связано с System.Data в такой манере, теперь перенесено в новое корневое пространство имен, System.Data.Entity.Core. Ряд классов перемещен в System.Data.Entity, скажем, EntityState, который сейчас находится в System.Data.Entity.EntityState. Например, пространство имен Mapping стало пространством имен System.Data.Entity.Core.Mapping, а Objects — System.Data.Entity.Core.Objects. Список исключений (классов, не попавших в System.Data.Entity.Core) см. в п. 4 документации Data Developer Center «Upgrading to EF6» (bit.ly/OtrKvA).
При обновлении существующих приложений под EF6 я просто даю возможность компилятору указать на эти изменения, выводя мне все ошибки в виде «The type or namespace… is not found», а затем я выполняю поиск и замену в рамках решения для корректировки пространств имен.
В качестве примера я начала с небольшого решения из второго издания своей книги «Programming Entity Framework». Это решение было написано под EF4, модель EDMX, генерируемые в коде POCO-классы сущностей и ObjectContext API. На тот момент Code First и DbContext API не существовали. Прежде чем приступить к рефакторингу, я удостоверилась, что приложение по-прежнему работает (отладка в Visual Studio 2013).
Хотя сейчас я сосредоточусь на более крупном шаге — переходе с EF4 на EF6, вы должны следовать по тому же пути исправления пространств имен, если собираетесь переходить с EF5 на EF6, поскольку все эти изменения в пространствах имен произошли где-то между EF5 и EF6. Переход с EF4 непосредственно на EF6 создает некоторые дополнительные трудности, и, кроме того, в моем старом приложении использовался шаблон T4, для которого больше нет прямой замены.
Мои шаги в обновлении с EF4 до EF6
Если вы получали Entity Framework через распространение NuGet-пакетов, то должны мысленно вернуться в то время, когда EF была просто частью .NET Framework и все ее DLL помещались в Windows Global Assembly Cache (GAC). Перед обновлением до EF6 я вручную удалила ссылки на System.Data.Entity (версии 4.0.0.0) в каждом проекте решения. Я также выполнила очистку решения (щелкнув правой кнопкой мыши решение в Solution Explorer и выбрав Clean Solution), чтобы быть уверенной в том, что любые исходные DLL, которые я могла принудительно поместить в папки BIN, удалены. Как оказалось, мое усердие было излишним, потому что установщик NuGet-пакета для EF6 сам удаляет старые ссылки.
Затем я использовала NuGet, чтобы установить EF6 в соответствующие проекты, и заново скомпилировала решение. Проблемы с пространствами имен быстро выявятся при компиляции из-за зависимостей проектов в ваших решениях. В моем решении поначалу проявилась лишь одна ошибка, связанная с пространством имен. Я исправила ее и опять запустила компиляцию решения, после чего увидела куда больше ошибок — все они, кроме одной, относились к пространствам имен. В этом одном случае компилятор выдал полезное сообщение, что редко применяемый атрибут, преимуществами которого я пользовалась (EdmFunction), переименован (а значит, помечен как устаревший — Obsolete) и заменен атрибутом DbFunction.
После серии итеративных исправлений и перекомпиляций, на которые у меня ушло всего несколько минут в этом небольшом решении, я смогла успешно скомпилировать и запустить приложение, предназначенное для просмотра, редактирования и сохранения данных.
Исправление проблемы с ObjectContext и шаблоном T4
Следует помнить о другой возможной задаче. В моем решении применялась EDMX — Entity Data Model, предназначенная для EF Designer и управляемая через него. Поскольку я создала ее в Visual Studio 2010 с использованием EF4, она опиралась на устаревший шаблон генерации кода, который создавал ObjectContext для управления всем, что относится к сохранению и кешированию данных. Этот шаблон генерировал POCO-классы (не имеющие зависимости от Entity Framework) на основе сущностей в модели. Если я вношу любые изменения в свою модель, я должна заново сгенерировать эти классы и контекст. Но этот устаревший шаблон (который генерирует контекст) ничего не знает о новых пространствах имен. Хотя существуют шаблоны DbContext и ObjectContext (плюс EntityObjects) (рис. 1), замены для моего шаблона, который предоставлял ObjectContext и комбинацию POCO-классов, нет. И чтобы стало еще интереснее, мне пришлось адаптировать этот шаблон. Поэтому вместо выбора нового шаблона, который не стал бы работать с моим приложением, я внесла два небольших изменения в шаблон Context.tt, который хранился в моем решении:
- в строке 42 выражение «using System.Data.Objects;» превратилось в «using System.Data.Entity.Core.Objects;»;
- в строке 43 выражение «using System.Data.EntityClient;» превратилось в «using System.Data.Entity.Core.EntityClient;».
Рис. 1. Вы найдете шаблоны для генерации DbContext плюс POCO-классы или ObjectContext плюс классы, отличные от POCO
Теперь в любой момент, когда я заново генерирую классы на основе модели, класс ObjectContext получает правильные пространства имен и мои сгенерированные POCO-классы продолжают работать в приложении. Заметьте, что в документации Data Developer Center, на которую я уже ссылалась, поясняется, как использовать поддерживаемые шаблоны.
Преимущества, которые вы получите без изменения любого другого кода
Я обнаружила, что обновление приложения на использование EF6 прошло без особых проблем. Однако нужно учитывать один очень важный момент. Хотя приложение теперь использует самую свежую версию Entity Framework, оно получает выигрыш только от оптимизаций в нижележащей Entity Framework, которые появились в версиях 5 и 6. Поскольку основная часть оптимизаций появилась в EF5, переход с EF5 на EF6 без применения других новых средств не даст заметного выигрыша. Чтобы узнать, что еще нового есть в EF6, прочитайте мою статью «Entity Framework 6: The Ninja Edition» в декабрьском номере за 2013 год (bit.ly/1qJgwlf). Многие усовершенствования относятся к DbContext API и Code First. Если вам нужно обновление до EF6, а также переход на DbContext, советую начать с простого обновления до EF6 и добиться корректной работы приложения в целом, а уж потом приступать к переходу на DbContext API. Это более сложный процесс, о котором я подробно расскажу в следующей статье из этой рубрики.
Даже при таких минимальных изменениях ваша кодовая база теперь готова к использованию более современных API.
Разбиение большой модели
Привыкли вы к созданию модели в EF Designer или к рабочему процессу Code First (см. «Demystifying Entity Framework Strategies: Model Creation Workflow» по ссылке bit.ly/Ou399J), модели с большим количеством сущностей могут вызывать проблемы на этапе разработки и даже в период выполнения. Мой личный опыт говорит о том, что большие модели слишком трудны в управлении. Если вы работаете в EF Designer и имеете множество сущностей (сотни), это приводит не только к тому, что дизайнер дольше открывает и отображает вашу модель, но и затрудняет визуальную навигацию по модели. К счастью, работа EF Designer ускоряется за счет некоторых возможностей Visual Studio 2012 (см. «Entity Framework Designer Gets Some Love in Visual Studio 2012» по ссылке bit.ly/1kV4vZ8).
Но даже в этом случае я всегда рекомендую, чтобы модели были меньше. В своей статье «Shrink EF Models with DDD Bounded Contexts» (bit.ly/1isIoGE) я рассказывала о преимуществах более малых моделей, а также о некоторых стратегиях их применения в приложении. Однако, если у вас уже есть большая модель, разбить ее на части может оказаться сложной задачей. У меня были клиенты, которые работали с моделями, полученными из огромных баз данных методом обратного инжиниринга, что давало в конечном счете 700 или даже 1000 сущностей. Модель EDMX можно уменьшить в дизайнере или с помощью Code First, но суть одна и та же: разбить модель на части трудно.
Ниже я дам несколько полезных советов в разбиении большой модели на меньшие, чтобы упростить сопровождение и потенциально повысить производительность.
Не пытайтесь проводить рефакторинг всей модели сразу. Для каждой извлекаемой малой модели потребуется проделать дополнительный рефакторинг кода из-за изменения ссылок и переделки части связанного кода.
Поэтому первый шаг — выявить раздел модели, который почти автономен. Не волнуйтесь по поводу некоторого перекрытия. Например, возможно, вы работаете над системой для компании, которая производит и продает товары. Программное обеспечение может включать функциональность для сопровождения специалистов по продажам — обработки данных о персонале, контактной информации, территориям продаж и др. Другая часть программного обеспечения может ссылаться на этих специалистов при формировании заказа клиента на основе определения территорий продаж, а еще одна часть — отслеживать комиссионные выплаты от продаж. Поэтому беритесь единовременно только за один конкретный сценарий, скажем, за сопровождение списка специалистов по продажам.
Ниже перечислены этапы перехода к меньшим моделям.
- Идентифицируйте сущности, участвующие в этом сценарии (рис. 2).
- Создайте совершенно новый проект.
- В этом проекте определите новую модель (либо через EF Designer, либо через Code First), которая будет содержать релевантные сущности.
- Идентифицируйте код приложения, участвующий в этом сценарии.
- Обновите релевантный код, который использует оригинальный контекст (для запросов, сохранения изменений или другой функциональности) так, чтобы он задействовал новый контекст, созданный вами.
- Выполняйте рефакторинг существующего кода до тех пор, пока не начнет работать целевая функциональность. Если у вас есть автоматизированные тесты, это может повысить эффективность рефакторинга.
Рис. 2. Сопровождение специалистов по продажам и территорий можно извлечь в отдельную модель с малым влиянием на другие сущности
В ходе этого процесса держите в уме дополнительные рекомендации:
- не удаляйте эти сущности из большой модели. Иначе вы слишком многое поломаете. Просто оставьте их там и на время забудьте о них;
- ведите журнал по тому, какой рефакторинг вам пришлось выполнять, чтобы добиться работы приложения с новой, меньшей моделью. Так вы увидите шаблоны, которые сможете применять для других узкоспециализированных моделей, выделяемых из оригинальной большой модели.
Итеративно добавляя в решение по одной малой модели единовременно и подправляя код под ее использование, вам будет гораздо легче. В большой модели те же сущности могут быть связаны отношениями, не имеющими ничего общего с задачей сопровождения SalesPerson. Например, у вас может быть отношение от SalesPerson к Sales Order, поэтому вы, по-видимому, не захотите полностью удалять SalesPerson из модели. Проще оставить урезанный тип SalesPerson только для чтения, используемый как ссылка при создании Order в одной модели, а полнофункциональный редактируемый тип SalesPerson (ничего не знающий об Orders) применять для сопровождения в другой модели. Когда все будет закончено, обе сущности могут по-прежнему указывать на одну и ту же базу данных. Если вы используете Code First и миграции, загляните в ранее упомянутую статью по сокращению EF-моделей.
В конечном счете у вас должно получиться много малых моделей и сохраниться часть ссылок на большую модель. В этот момент будет легче понять, какие сущности больше не используются в большой модели и могут быть безопасно удалены.
Терпение: да, это добродетель
Итак, самое важное в сухом остатке — необходимость пошагового подхода к обновлениям и рефакторингу с проверкой работоспособности приложения на каждой итерации. Рассматривайте обновление до более новой версии Entity Framework как отдельный рабочий элемент всего процесса. И даже его можно разбить на две части, сначала сосредоточившись на включении новых API и тестировании работоспособности кода, а уж потом изменяя код так, чтобы он мог задействовать новые средства. Теми же детскими шажками разбивайте большую модель, особенно если вы планируете обновление с ObjectContext на DbContext. Извлекайте малые модели и перерабатывайте их, проверяя, что релевантная логика может работать с новой, меньшей моделью. Как только приложение начнет работать, наступит пора ослабить вашу привязку к ObjectContext, что поначалу нарушит функционирование большой части кода. Но по крайней мере, этот «сломанный» код будет гарантированно ограничен гораздо меньшей областью вашей кодовой базы, и вам придется перерабатывать меньше кода за один раз.