Home .NET Entity Framework through the eyes of an outsider

Entity Framework through the eyes of an outsider

by admin

Background

And there’s a lot, a lot of writing on Entity Framework. In fact, it’s the "default" ORM for .NET and the Microsoft Visual Studio environment…
So, one day, not too long ago, I got my hands on a no small project. At the time of my arrival, the project was about three years old. Technically, it was a heartbreaking sight. A rough measurement showed that the web application easily and quite unnecessarily managed to consume about 1GB of server memory per concurrent user. I had to quickly and quickly figure out what was so interesting about it. Good lesson on what not to do.
This project used the Entity Framework to access data in a relational database. Besides the obvious architectural and programming nonsense like ToList() for any reason, Include anything, impose restrictions on multiple objects not on database level, but on application level using LINQ To Objects, there were problems related only to Entity Framework.
I confess that I can’t count myself among the active users and experts of Entity Framework, because in practical life I’ve been using other ORMs for many years. But this is a good thing, because I can tell you, dear readers, about the peculiarities of Entity Framework and related tools, noticed by the eyes of an outsider. I can’t tell you everything, but I will tell you what I see now that I have tried this Entity Framework.

Measure seven times or the horror of designing

CodeFirst and DBFirst are not suggested. And here’s why. The answer here is both philosophical and practical.
CodeFirst. It certainly gives you the most advanced features in the Entity Framework and full control. Some would argue for this approach until the end of a fight. My argument is simple: as long as you have 5-10-15 entities you can keep the system structure in mind. Enjoy it! When it goes into the hundreds, you can’t anymore. It’s really easy for me to count into the hundreds, and I can’t fit that much into my head. In the heads of comrades too, especially when you enter a new person in the project. That’s why I need pictures. Compact, clear, easy to use. And the model should be not on one picture, but on several, piece by piece, selected somehow subjectively. And, furthermore, from pictures something should be generated somehow, I will not write the same kind of code hundreds of times, and do not make mistakes.
How about DBFirst? Nope, that’s not for me either. How am I going to do inheritance? And then, how will I think about the application through the specifics of the database? The data types are different there, too. If I write, say, VARBINARY, then I’ll have to remember in my head that somewhere it’s an image, and somewhere it’s a hash. Well, and document it separately, or do some folklore with my comrades. Let’s multiply that by 300+ entities. No, it’s inconvenient. No, I want to think in applied types. And then, what will be left after me?
That leaves ModelFirst. Here I can at least draw entities close to what I get in C#, and use .NET types for fields. But here, too, there’s a carefully placed, rampant rake :

  1. Designer. Why do the links on it jump around? And run through the diagram as Visual Studio likes, not as I like, and in some cases they are knotted? I want them to be nailed to the sheet and not move anywhere without my knowledge, otherwise I can’t find them after opening the diagram again. I think I understand why CodeFirst is so popular.
  2. How do you just cut a model into multiple diagrams? Well, I’m not comfortable with 300 entities on one diagram, and besides, I’m tired of clicking my mouse on sliders. And it really slows down the designer with so many entities. Sure, there’s the "Move to new diagram" feature, but how can I move some entities around or use the same entities on multiple diagrams if I need to?
    The gurus say there is some way, there is. It comes from the realm of manually editing a .diagram file. But you have to know the higher plan. But I’m not a guru, I don’t have time to get enlightened, I need to get applied functionality.
  3. For example, I drew a field of type string. In the database it is generated by default as nvarchar(max). I would like to give the engineer who invented this default a generous flick. Just for this default. From the bottom of my heart. In the properties of EntitySet field you can’t change the type even for one case. In fact I want to change this default so all string fields will be generated by another database type. What to do? Edit in edmx text editor for all and then clean up the subsequent glitches?
  4. I still can’t draw using my application types, the type selection is limited in the designer. What if I came up with my own application type in the design process (or already have one ready) and just want to explain to the ORM how it maps to the base and how it maps to C# code? Well, of course I would have to follow some rules when implementing the type, like having implicit conversions to the native type, or implementing the ancient IConvertible into something.
  5. What should I do if I want C# code corresponding to entities to contain something special? And the suggestion in that case is to go back to CodeFirst. Or welcome to the wondrous, new world of T4.
  6. Editor-designed inheritance is not decoupled into a "human" TPC (a separate table under each class with all the fields), but only as :
    – TPT (separate table under each class with only its fields + link to ancestor). Then you will have : JOINs for each inheritance relation – performance trouble and headache when trying to form a constraint in bare SQL;
    – TPH (put all levels in one table, ignoring normalization and other pure ideals). I wonder how anyone could even think of such a thing.
    If you still want TPC, see again. CodeFirst + ModelBinder. I’ll take this opportunity to say hello to that engineer. Give him a fat flick on the number of application entities.

To understand how "convenient" to design, I suggest talking to someone trying to adapt Entity Framework design tools to, say, Oracle. Especially making changes to an existing model. I recommend finding someone of the opposite sex for this purpose. Contrasting worldviews will spice up your discussion.

Where are the keys to the tank or "entertaining" programming

Well, let’s see how it’s programmed.
Let’s start simple : create and save something :

CDLIBEntities dbctx = new CDLIBEntities();//Country ctr1 = dbctx.Country.Create();Country ctr1 = new Country();ctr1.primaryKey = Guid.NewGuid(); //Ah, yes! We should not forget to manually generate the primary key.ctr1.Name = "Greece";//Publisher pblshr1 = dbctx.Publisher.Create();Publisher pblshr1 = new Publisher();pblshr1.primaryKey = Guid.NewGuid(); //Ah, yes! Generate key again...pblshr1.Name = "First Publisher";pblshr1.Country = ctr1.primaryKey; //Dammit, I forgot again, I have to, I have to remember the keys and put them everywhere manually, otherwise EF will think that the referential integrity is brokenpblshr1.Country1 = ctr1;//dbctx.Publisher.Attach(pblshr1); //AU! Why should I even care what type of entity I have here? Because it's already clear what type it is.dbctx.Publisher.Add(pblshr1); // Ah, Attach doesn't set entities to create status, I also need to remember to do Add to new objects and Attach to old ones, otherwise everything will break// Or maybe the status of the entity will somehow recognize itself?dbctx.SaveChanges();

It’s amazing here :

  1. You have to manually initialize the primary keys. Wouldn’t it be better to have some separately prescribed pattern for primary key generation? Well, and management capabilities;
  2. To bind entities, you not only need to set the navigation property, but also set the foreign key correctly, otherwise Entity Framework will complain about the violation of referential integrity. It’s not clear: if each entity is identified by a key, then linking via a public field should automatically mean a relational relation. It’s so easy, just to signify a public field, but no, come on, dear programmer, remember that we have primary keys and foreign keys, signify sit like there’s nothing else to do;
  3. Yes, there is an approach where keys for objects are created by themselves in the database. On the one hand, why not, especially if the key is integer, but on the other hand, until I execute a save request, I have no way to figure out what the key will be and use it further. This is especially fun to handle when the constructed object is needed in memory and with a key, but whether it needs to be saved in the database is determined at the very end.
  4. Attaching or adding objects to contexts is done only by calling a method on a collection of the corresponding type. And different methods are used: for adding – Add, and for attaching an existing one – Attach. What’s so "convenient"? Maybe in the question of what, where and how to add, Entity Framework with .Net somehow without me will understand what type of entity and what happened to it?
  5. Why is there a context? To feed the programmer’s brain with more "interesting" puzzles? For example, to tuck in instances of some stub instead of entity classes themselves from the context. Or, for example, what happens if you try to link objects taken from different contexts? That would probably be fun. So I have to remember what context I got from.

That’s a lot of gestures for such a simple task as constructing and linking entities to each other.

How to ask a DBMS or "under-include"

Let’s see what happens when Entity Framework reads something from the database. This is also very interesting. How and when it builds and executes queries.
Take a look at the edmx (slice) to visualize the task a bit :
Entity Framework through the eyes of an outsider
By the way, the function of exporting the model into a picture is implemented to not only give the developer a lot of fun and enjoyable moments, but also to brighten up his leisure time. If you export a relatively large model, the resolution of the picture will be about :
Entity Framework through the eyes of an outsider
In principle, I concede that there will be people who can discern this. Here it says "BlueRay" in the title of the entity.
And then, did you notice that I had to indicate the link capacities by "scribbling" on the picture "by hand", because they are not visible? I did it on purpose "scarier". Do you think that the export procedure contains an error and the power just did not get into the diagram? No, it’s simpler than that, there is no error, the power honestly exported light gray symbols on white background (look carefully under the ends of the links). This is probably done to test the color resolution of monitors, as well as to train the color perception of developers.
We want to output the title of the disk, the name of the publisher and the name of the country. There is a temptation to solve the problem head-on. Why, it is convenient. We take a collection out of context, go through the links. All data is somehow swapped out of the database by itself. You don’t have to think about it, it all works, the goal is achieved, what else do you need?
After realizing this simple construction, let’s see what queries EF performs in this case.

CDLIBEntities dbctx = new CDLIBEntities();dbctx.Database.Log = (s => { textBox1.AppendText(string.Format("{0}{1}", s, Environment.NewLine)); }); //This is how we will peek at what queries EF is runningDVD[] dvds = dbctx.DVD.ToArray(); // Let's see how many queries this way//DVD[] dvds = dbctx.DVD.Include("Publisher1").Include(@"Publisher1.Country1").ToList().ToArray(); //In this variant there will be one query with joins, but do we need to read ALL fields from linked tables? In practice, this is a nightmare with horrorfor (int i = 0; i < dvds.Length; i++ ){textBox1.AppendText(string.Format("{0} {1} {2}", dvds[i].Name, dvds[i].Publisher1.Name, dvds[i].Publisher1.Country1.Name );textBox1.AppendText(Environment.NewLine);}textBox1.AppendText("OK" + Environment.NewLine);

First, let’s check "head-on", let’s run it: look, Entity Framework executes as many as 5 queries, and each one inside its own conception as well.
Textbox output :

 Opened connection at 17.04.2015 11:01:19 +05:00SELECT[Extent1].[primaryKey] AS [primaryKey], [Extent1].[Version] AS [Version], [Extent1].[Capacity] AS [Capacity], [Extent1].[Name] AS [Name], [Extent1].[Publisher] AS [Publisher]FROM [dbo].[DVD] AS [Extent1]-- Executing at 17.04.2015 11:01:20 +05:00-- Completed in 11 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:01:20 +05:00Opened connection at 17.04.2015 11:01:20 +05:00SELECT[Extent1].[primaryKey] AS [primaryKey], [Extent1].[Name] AS [Name], [Extent1].[Country] AS [Country]FROM [dbo].[Publisher] AS [Extent1]WHERE [Extent1].[primaryKey] = @EntityKeyValue1-- EntityKeyValue1: '5cba87e2-2809-4437-9ff3-5abfe0d21536' (Type = Guid, IsNullable = false)-- Executing at 17.04.2015 11:01:20 +05:00-- Completed in 6 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:01:20 +05:00Opened connection at 17.04.2015 11:01:20 +05:00SELECT[Extent1].[primaryKey] AS [primaryKey], [Extent1].[Name] AS [Name]FROM [dbo].[Country] AS [Extent1]WHERE [Extent1].[primaryKey] = @EntityKeyValue1-- EntityKeyValue1: '888c8fd2-1a12-4ec4-90fa-9742c29cae9e' (Type = Guid, IsNullable = false)-- Executing at 17.04.2015 11:01:21 +05:00-- Completed in 2 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:01:21 +05:00Movie 3 Second Publisher USAOpened connection at 17.04.2015 11:01:21 +05:00SELECT[Extent1].[primaryKey] AS [primaryKey], [Extent1].[Name] AS [Name], [Extent1].[Country] AS [Country]FROM [dbo].[Publisher] AS [Extent1]WHERE [Extent1].[primaryKey] = @EntityKeyValue1-- EntityKeyValue1: '65e0fb16-15aa-4591-8c8b-286e498d1203' (Type = Guid, IsNullable = false)-- Executing at 17.04.2015 11:01:21 +05:00-- Completed in 0 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:01:21 +05:00Opened connection at 17.04.2015 11:01:21 +05:00SELECT[Extent1].[primaryKey] AS [primaryKey], [Extent1].[Name] AS [Name]FROM [dbo].[Country] AS [Extent1]WHERE [Extent1].[primaryKey] = @EntityKeyValue1-- EntityKeyValue1: '658e951e-b56a-4423-b16a-b1dd2c7c293a' (Type = Guid, IsNullable = false)-- Executing at 17.04.2015 11:01:21 +05:00-- Completed in 0 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:01:21 +05:00Movie 0 First Publisher GreeceMovie 1 Second Publisher USAMovie 4 First Publisher GreeceMovie 2 First Publisher GreeceOK 

This is from EF’s "give what you grab for" approach to navigability. There is a hard price to pay for this development convenience: in large and complex systems you cannot do so, as it wastes the resources of both the DBMS and the application.
It’s good that EF has some tools to control the loading. Let’s uncomment Include. Praise the wisdom of EF! Now we have JOIN for every Navigation and only one request.
Textbox output :

 Opened connection at 17.04.2015 11:03:44 +05:00SELECT1 AS [C1], [Extent1].[primaryKey] AS [primaryKey], [Extent1].[Version] AS [Version], [Extent1].[Capacity] AS [Capacity], [Extent1].[Name] AS [Name], [Extent1].[Publisher] AS [Publisher], [Extent2].[primaryKey] AS [primaryKey1], [Extent2].[Name] AS [Name1], [Extent2].[Country] AS [Country], [Extent3].[primaryKey] AS [primaryKey2], [Extent3].[Name] AS [Name2]FROM [dbo].[DVD] AS [Extent1]INNER JOIN [dbo].[Publisher] AS [Extent2] ON [Extent1].[Publisher] = [Extent2].[primaryKey]INNER JOIN [dbo].[Country] AS [Extent3] ON [Extent2].[Country] = [Extent3].[primaryKey]-- Executing at 17.04.2015 11:03:45 +05:00-- Completed in 16 ms with result: SqlDataReaderClosed connection at 17.04.2015 11:03:45 +05:00Movie 0 First Publisher GreeceMovie 4 First Publisher GreeceMovie 2 First Publisher GreeceMovie 1 Second Publisher USAOK 

All or nothing

But here’s the trouble : if we just write Include, it reads ALL properties of both its own and related entities. This seems to be the key to understanding where memory and speed goes. "Over-include, " so to speak. Calling Include head-on to anything is scary, especially branching out to a lot of navigation.
Let’s try to do it better. Solution: select a set of only the required fields of the entities and read only them. Such a set can be called a projection or a representation, whatever you want. It’s good that Entity Framework has such a feature, but it’s a bit of a curve.
Reading in projection :

CDLIBEntities dbctx = new CDLIBEntities();dbctx.Database.Log = (s => { textBox1.AppendText(s); })// Binding of anonymous types (binding because we will have to make anonymous types for navigation properties, too)var anon = dbctx.DVD.Select(x => new { primaryKey = x.primaryKey, Name = x.Name, Publisher1 = new {primaryKey = x.Publisher1.primaryKey, Name = x.Publisher1.Name }).ToArray();// 2. Declared types (similar to the anonymous one, only we explicitly declare the types, let's omit the example, it's obvious)// 3. Take and make descendants of the required entities (although there is no need to explicitly type them)DVD_D[] dvd_derived = dbctx.DVD.Select(x => new DVD_D { primaryKey = x.primaryKey, Name = x.Name, Publisher1 = new Publisher_D { primaryKey = x.Publisher1.primaryKey, Name = x.Publisher1.Name }).ToArray();

There are 3 options :

  1. Declare an anonymous type and select only what you want in it;
  2. We write a special class with only needed properties and use it;
  3. Make descendants of the desired entities and populate the selection in them.

What’s inconvenient here : Are we really going to write for every sample like this? Is this productive?
And then: our goal is not to read, but to WORK with entities, i.e. to write logic which results in changing entity field values. It should be easy to do: change the value of a property of an entity, and then call the context to save it when needed, like this:

// I really want to write something likedvd_derived[0].Name = "Green Weenie";dbctx.SaveChanges();

The problem is that this object is not created out of context and its EFu type is unknown. Now how do we save the changes?
The curvature is that it is completely unclear how to save changes without taking the object out of context FULLY, with all the fields.
So what to do? People invent! For example, they write classes for each projection case and then mapp them somehow into entity classes (understandable by context) and back again. You could also have separate sets of entity classes with sets of properties corresponding to the samples and customization to the same tables. You could, you could somehow get twisted. It must be a fascinating activity. But the essence of this exercise does not clash with the correct philosophy, which is that ORM is ORM, that the constructions in the programming language unambiguously represent the subject entity (OBJECTS). Not that some of it is there, some of it is there. Here we read this way, here this way, here we map, here we cut, here we glue, here we wrap fish. If I’ve got an entity in the code, I have to be pretty sure that that’s all I need, and there’s nothing else in the code that’s responsible for that same entity. Divide and conquer, no need to smear functionality. Otherwise it’s not an ORM, it’s just a classic set of crutches.

In lieu of a conclusion

The purpose of using ORM is to focus the programmer on the subject matter, without losing productivity for both the programmer and the software he or she develops. Productivity should increase in general. It is the 21st century, and the question of data access is still not closed. As it is, this is all pretty for school crafts on 10-20 entities, and is certainly not appropriate for any serious task.
In short, I don’t get it. Well, okay, for me Entity Framework is a gimmick from legacy projects, we ourselves use another product, not at all mass and not promoted, but how do most people live? After all, people all over the world are working and enjoying life, somehow writing and giving products? Well, in general, quietly sleep at night. After all, I just started, and everything crumbles in my hands. Either serious, large projects are written on something else (I wonder what), or they write everything by hand, which is generally hard to believe. How do you people live, I don’t understand…
I have another wagonload of questions to spare. But perhaps I’d better tell you in my next article which ORM seems convenient to me, "best practices, " as it’s fashionable to say these days. Eh?

You may also like