2sxc 9 is bringing the future to DNN with Angular 4 and EF Core. In this post I want to tell you more about our work with EF Core and lessons learned.
If you want to know more about running .net Core on DNN and how it's architected, check out our blog post about Future-Proofing DNN with .net Core.
2sxc 9.0 with a new .net Core ORM
In 2sxc 9.0 we replace the entire Object / Relational Mapper (ORM, which means the data-access-layer) with Entity Framework 1.1. We did it in these steps:
- Refactored the entire data and business layer, to clean up and again fully separate the layers (in the last few years, various code had started to violate the layer separation) - this took us about 10 days
- Created a parallel EF Core project, which duplicated the EF 4 functionality - this took about 1 day
- Refactored the business logic of the read-layer to EF Core - about 2 days
- Refactored the Create/Update/Delete business logic - about 5 days
- Testing, debugging, fixing - about 5 days
- Fixing EF Core bugs (it's not perfect yet) - 3 days
It was more than 25 days of work - mainly because the EAV is such a complex, rich system, but also because EF Core still has bugs and is simpler than Entity Framework 4.
Let's now look at some lessons learned.
The Great: Future-Proof
Working with EF-Core is very intuitive and lean - which feels a lot like node or other JS-programming, where a mini-tool does something very well. We're also very proud to have created a working solution using .net core libraries, ready for the future 2 years ahead of DNN.
The Good: EF Core is ca. 2x faster :)
This should come as no surprise: Entity Framework Core is much lighter than Entity Framework 4 or 6, which also means it doesn't do as much - and this it does faster. In terms of loading a complex data structure into the web server cache, we believe that the core parts are now 2x faster.
The Bad: EF Core has no Lazy Loading :(
At least 5 days of our refactoring time was necessary because of the missing lazy loading. Very bad. Let me explain what hit us, so you can avoid this as well.
- In the full entity framework, we were able to query the DB - like this:
var apps = DbContext.App.Single(e => e.AppId == 27)
- We could then pass this apps object around, and in other methods do:
var person = app.ContentTypes.Where(ct => ct.Name == "Person");
This worked, because EF 4 and higher would then automatically go back to the DB and load all ContentTypes-records related to this app. In Entity Framework Core, this doesn't happen. The system will believe that apps has zero content types (as they were neither pre-loaded nor lazy loaded), and the code will continue to work - but with incorrect assumptions.
The solution is technically simple, but causes a lot of issues. Here's what we would have to do in EF Core 1.1 for the case above:
- Pre-loading Content Types:
var apps = DbContext.App.Include(a => a.ContentTypes).Single(e => e.AppId == 27);
This sounds simple, but in reality it causes a lot of issues, because ofter you'll have helper commands like:
- GetEntity(int entityId)
- GetEntitiesOfType(string typeName)
...which are used in a lot of code, each scenario requiring different related information. So either we change GetEntity(...) to get everything all the time (slowing down the system) or we have to increase the complexity of the other code.
This one was tough - and there is no magic bullet :(. This is also one of the main reasons why the EF-Core team says EF-Core isn't yet the generally recommended EF for everyone.
The Ugly: It can have Bugs
While developing I ran into 2 hard-to-spot bugs which cost me at least 3 days work. The hard thing wasn't living with the bugs - they were easy to solve - but finding out what was going on. Here are the two I ran into:
1. Randomly truncating a string to 450 characters
I used DB-First and created by EF-Core Model using the recommended nugets and powershells - which was easy. But something in that setup caused my code-model to think that an important nvarchar(max)-field - a string with unlimited amount of text - was part of an index. This wasn't obvious from the code, and everything looked fine - but some rare update commands (not all) truncated my string to 450 characters when saving, because that's the default behaviour for strings which are used in a key. This cost me almost 2 days to figure out.
2. Boolean with Default true cannot save an initial false-value
My EF-Core code-model also found out that my DB had some boolean fields with a default "true" - which looks innocent in code like HasDefaultValueSql("1"). Until you want to create new records which place a false in that field - in my case this was to save draft-item (IsPublished=false) - which was always saved as true. This is an issue with the change-detection on the EF-Core model, which assumed that a false-boolean must be un-initialized, so not saving anything (causing the DB to fallback to true). It's this issue.
In Summary we wanted to show that it can be done, and we wanted to be sure any future work on 2sxc and EAV are ready for the future, because we have a lot in store, and we didn't want to cary the technical debt into the future.
Love from Switzerland,
PS: Try it on your code, or check out our eav-server on Github