{"id":77404,"date":"2018-02-26T13:13:54","date_gmt":"2018-02-26T13:13:54","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=77404"},"modified":"2018-11-13T14:01:43","modified_gmt":"2018-11-13T14:01:43","slug":"first-experience-migrating-net-app-core","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/first-experience-migrating-net-app-core\/","title":{"rendered":"My First Experience Migrating a .NET App to Core"},"content":{"rendered":"<p>I recently migrated an old .NET Framework 4.5.2 app to .NET Core 2.0. This isn\u2019t a guide to that process and certainly isn\u2019t an exhaustive list of all the things that can go wrong, but rather provides an account of what worked for me, where the pitfalls were, and whether it was worth it.<\/p>\n<h2>The application<\/h2>\n<p>The app essentially <a href=\"http:\/\/www.benemmett.co.uk\/SurveyMonkey\/\">imports and processes data from SurveyMonkey<\/a>. A <em>DataPersistence<\/em> project uses Entity Framework 6.2 to handle all database access. The logic for communicating with SurveyMonkey and transforming the data, as well as various administration functions lives in the <em>ImporterCore<\/em> library. <em>Importer<\/em> is a very thin command line app which wraps some functionality from <em>ImporterCore<\/em>, allowing it to be run easily as a scheduled Windows task. The <em>Explorer<\/em> project is an ASP.NET MVC 5 website to explore the data. A <em>Tests<\/em> project (not included in the diagram) is built on nUnit 3, and brings the total projects in the solution to 5.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1212\" height=\"536\" class=\"wp-image-77405\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/02\/word-image-35.png\" \/><\/p>\n<h2>Overview of the process<\/h2>\n<p>It ended up taking just over 2 days and looked like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"642\" height=\"199\" class=\"wp-image-77406\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/02\/word-image-36.png\" \/><\/p>\n<p>&gt;80% of the effort was spent reading blogs, trial &amp; error, making mistakes, and drinking more coffee than I\u2019m proud of. Doing this again would take at most a quarter of the time \u2013 if anyone on your team has done this already I strongly recommend you buddy up! The general approach I ended up taking was:<\/p>\n<ol>\n<li><s>Break everything, Google solutions for every problem in turn until stuff worked again.<\/s><\/li>\n<li>For all but the web project, <a href=\"http:\/\/www.natemcmaster.com\/blog\/2017\/03\/09\/vs2015-to-vs2017-upgrade\/#approach-two-manually-convert\">upgrade the csproj files<\/a> to the new (much simpler) VS15 format, but still building for .NET 4.5.2. I chose to do this manually rather than creating new projects from scratch.<\/li>\n<li>Unload all projects in the solution apart from the DataPersistence library, which was at the bottom of the pyramid. Make that build for .NET Core instead.<\/li>\n<li>Update all the packages in the DataPersistence library to the latest versions which supported .NET Core, or for some cases like Entity Framework, replace the packages entirely with their .NET Core equivalents (Entity Framework Core in this case).<\/li>\n<li>Go through all build failures and patch up api changes until the project compiles.<\/li>\n<li>Repeat steps 2-4 but adding additional projects back into the solution one-at-a-time.<\/li>\n<li>For the web project there\u2019s so much plumbing to get everything to work that I just created a new empty project and manually copied over the Controllers, Models, Views, javascript and css, with just a few adjustments. Doing that rather than trying to upgrade the project in-place was definitely the right decision.<\/li>\n<li>Run the tests. Weep at the multitude of ways that \u201cbuilding just the same as before\u201d does not mean \u201cdoing what it did before\u201d.<\/li>\n<li>Fix up the issues from step 7, ranging from one-liners through to pretty invasive changes which touch dozens of files.<\/li>\n<li>Test manually.<\/li>\n<\/ol>\n<h2>Gotchas<\/h2>\n<p>Plenty of things went wrong. Most of these focus on issues I encountered during steps 7 and 8.<\/p>\n<p><strong> Incompatible libraries <\/strong><\/p>\n<p>ImporterCore depended on a <a href=\"https:\/\/github.com\/bcemmett\/SurveyMonkeyApi-v3\">library<\/a> I originally wrote a couple of years ago, which didn\u2019t yet support .NET Core. It uses WebClient, which didn\u2019t exist in .NET Core 1.0 \/ 1.1, so getting this working would previously have been painful. Fortunately, WebClient was added into .NET Core 2.0, making the upgrade simple \u2013 just <a href=\"https:\/\/github.com\/bcemmett\/SurveyMonkeyApi-v3\/pull\/63\/files\">some changes to the csproj, AssemblyInfo, and nuspec files<\/a>. But if you depend on any incompatible libraries which you don\u2019t control, you could be blocked.<\/p>\n<p><strong> Entity Framework <\/strong><\/p>\n<p>This is what took most of the effort. Entity Framework 6.2 isn\u2019t available for .NET Core, and its replacement EF Core has significant changes, which makes it more like a port than an upgrade. (Here is a <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/efcore-and-ef6\/features\">feature list comparison<\/a>) First though, a happy point:<\/p>\n<p><strong>Mapping conventions<\/strong><\/p>\n<p>Overall I\u2019ve grown to like EF Core more than EF 6.2. The main reason is an emphasis on configuration by convention. Here\u2019s a mapping file for an original <em>Survey<\/em> object. It tells Entity Framework the names of columns for each property, the table name, and which item is the key (plus more in most cases).<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class SurveyMap : EntityTypeConfiguration&lt;Survey&gt;\r\n  {\r\n      public SurveyMap()\r\n      {\r\n          \/\/ Primary Key\r\n          this.HasKey(t =&gt; t.Id);\r\n          \/\/ Table &amp; Column Mappings\r\n          this.ToTable(\"Surveys\");\r\n          this.Property(t =&gt; t.Id).HasColumnName(\"Id\");\r\n          this.Property(t =&gt; t.Title).HasColumnName(\"Title\");\r\n          this.Property(t =&gt; t.Nickname).HasColumnName(\"Nickname\");\r\n          this.Property(t =&gt; t.Category).HasColumnName(\"Category\");\r\n          this.Property(t =&gt; t.Language).HasColumnName(\"Language\");\r\n          this.Property(t =&gt; t.IsOwner).HasColumnName(\"IsOwner\");\r\n          \/\/And a dozen or so more properties\r\n      }\r\n  }<\/pre>\n<p>In EF Core, by convention a property is assumed to map to a column of that name unless you tell it otherwise, just as the overall object is assumed to map to a table of the same name. It\u2019s also assumed that if there\u2019s a property called <em>Id<\/em> or <em>SurveyId<\/em>, then that is the Primary Key unless you tell it otherwise. So I got to delete nearly a thousand lines of unnecessary boilerplate, which is pretty cool.<\/p>\n<p>Most of the remaining mapping can be done through annotations (for example to tell Entity Framework about <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/modeling\/generated-properties\">values which are generated by SQL Server<\/a>), though a few things like <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/modeling\/keys#fluent-api\">composite keys<\/a> can only be configured using the fluent api, which is done in the context\u2019s <em>OnModelCreating(ModelBuilder modelBuilder)<\/em> method.<\/p>\n<p><strong>Api changes<\/strong><\/p>\n<p>There are a handful of api changes \u2013 for example foreign keys used to be configured with something like:<\/p>\n<pre class=\"lang:c# theme:vs2012\">modelBuilder.Entity&lt;ResponseQuestion&gt;()\r\n      .HasRequired(parent =&gt; parent.ResponsePage)\r\n      .WithMany(child =&gt; child.ResponseQuestions)\r\n      .HasForeignKey(prop =&gt; new\r\n          {\r\n              prop.ResponsePageId,\r\n              prop.ResponseId\r\n          }\r\n      );<\/pre>\n<p>In EF Core, <em>HasRequired()<\/em> was changed to <em>HasOne()<\/em>. The tests also used <em>context.Database.Create()<\/em> and <em>context.Database.Delete()<\/em>, which in EF Core were changed to <em>context.Database.EnsureCreated()<\/em> and <em>context.Database.EnsureDeleted()<\/em>, but these were pretty minor.<\/p>\n<p><strong>Interception<\/strong><\/p>\n<p>Slightly more effort was getting some custom handling of <em>DateTime<\/em> values working nicely. The application always stores <em>DateTimes<\/em> as Utc in the database, but when reading from the database Entity Framework doesn\u2019t know this, so treats them as <em>DateTimeKind.Unspecified<\/em>, which would lead to some incorrect behaviour elsewhere in the app. In the original version of the application, I worked around that with a variation of <a href=\"https:\/\/stackoverflow.com\/questions\/40205893\/datetime-kind-set-to-unspecified-not-utc-upon-loading-from-database\/40349051#40349051\">this technique<\/a> \u2013 essentially using the Interception capabilities in Entity Framework to patch up the <em>DateTime<\/em> object\u2019s <em>DateTimeKind<\/em> property. Unfortunately, this kind of Interception isn\u2019t yet possible in EF Core, so instead I used an EntityMaterializerSource\u00a0with some adaptations to let it handle nullable columns. Conveniently the old version of the application had tests in place to cover this conversion in a variety of scenarios (in particular because projection queries behave differently to entity queries) so it was easy to be sure it was working as expected.<\/p>\n<p>As an aside, it drive me crazy that neither version of Entity Framework handles this well out of the box \u2013 I can\u2019t remember the last time I stored date information in a database without wanting to consider it as UTC.<\/p>\n<h2>Lazy Loading<\/h2>\n<p>This was the biggest time suck: EF Core doesn\u2019t support lazy loading. It\u2019s coming in EF Core 2.1, due Q1 \/ Q2 2018, but that\u2019s no help today. I\u2019ve written a fair bit about <a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/net-tools\/entity-framework-performance-and-what-you-can-do-about-it\/\">Entity Framework performance<\/a>, and the original choice to use lazy loading was for good reasons. There\u2019s no indication anything\u2019s going wrong when building the app \u2013 it\u2019s just that when accessing an entity\u2019s child property which you expect to be lazily loaded, you get an empty list instead. Fortunately there were quite a few end to end tests using a live database which failed all over the place, but I could imagine this not being noticed in a situation where an app only very occasionally uses lazy loading (possibly without the developer even knowing), where there weren\u2019t tests in place.<\/p>\n<p>The solution of using eager loading instead wasn\u2019t the end of the world, but it did result in a lot of extra testing, generally messier code with deeply nested <em>Include()<\/em> and <em>ThenInclude()<\/em> statements all over the place, and is slightly less performant overall. I\u2019ll probably undo these changes in a few months once upgrading to v2.1 is possible.<\/p>\n<h2>Configuration<\/h2>\n<p>While .NET Framework configuration is typically stored as xml in app.config \/ web.config files, .NET Core uses appsettings.json to store configuration data. I actually like this change a lot, but it did require some changes.<\/p>\n<p>Hosting in IIS<\/p>\n<p>The original Explorer website was hosted under IIS, and I didn\u2019t realise that IIS doesn\u2019t know how to serve ASP.NET Core websites out of the box. ASP.NET Core uses the Kestrel server, which runs as a separate process to IIS. You need to install the <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/host-and-deploy\/iis\/?tabs=aspnetcore2x\">.NET Core Windows Server Hosting Bundle<\/a> which lets IIS handle things like security and some management tasks while handing off to Kestrel to actually run the code. The application pool needs to be configured to run No Managed Code too, since that\u2019s handled by Kestrel.<\/p>\n<p>Unfortunately I discovered this the hard way by deploying it to production and wondering why it didn\u2019t work. Nor did I have access to install components on the server, so there was a little downtime while I waited for a friendly sysadmin to help out. Oops.<\/p>\n<h2>Verdict<\/h2>\n<p>I didn\u2019t encounter any roadblocks; just a sequence of minor issues which each caused a small amount of hassle. The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/analyzers\/portability-analyzer\">Portability Analyzer<\/a> lets you know if this will be the case before doing the work \u2013 you should definitely run it before you start.<\/p>\n<p>This isn\u2019t a huge app \u2013 5 projects with a few tens of thousands of lines of code, much of which is boilerplate \/ framework stuff. Were I to do this again it would be a <em>lot<\/em> quicker than the couple of days it took me this time around, so if you\u2019re considering migrating a larger application I\u2019d recommend practicing on a smaller one like this first, or teaming up with someone who\u2019s done it before if you can.<\/p>\n<p>Ultimately I had little choice but to do this migration because of the need to interact with some other .NET Core systems. If it weren\u2019t for that I probably wouldn\u2019t have bothered \u2013 while it\u2019s nice to use the shiny new thing and nothing\u2019s worse than before (at least not that I\u2019ve noticed yet!), I haven\u2019t noticed it make anything easier either.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I recently migrated an old .NET Framework 4.5.2 app to .NET Core 2.0. This isn\u2019t a guide to that process and certainly isn\u2019t an exhaustive list of all the things that can go wrong, but rather provides an account of what worked for me, where the pitfalls were, and whether it was worth it. The&#8230;&hellip;<\/p>\n","protected":false},"author":19615,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[],"coauthors":[48331],"class_list":["post-77404","post","type-post","status-publish","format-standard","hentry","category-blogs"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77404","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/19615"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=77404"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77404\/revisions"}],"predecessor-version":[{"id":81677,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/77404\/revisions\/81677"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=77404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=77404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=77404"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=77404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}