📜 ⬆️ ⬇️

Entity Framework through the eyes of a stranger

Prehistory


But many, many write on the Entity Framework. In fact, this is the default ORM for .NET and the Microsoft Visual Studio environment ...

So, once, not very long ago, one rather big project came into my hands. By the time of my appearance, the project was about three years old. Technically, it was a heartbreaking sight. Rough measurement showed that the web application easily and completely unjustifiably managed to consume about 1GB of server memory per simultaneous user. I had to quickly and quickly figure out what was written there so interesting. A good lesson is how not to do it.

In this project, the Entity Framework was used to access data in a relational database. In addition to the obvious architectural and programmer nonsense, like ToList () for any reason, Include anything, imposing restrictions on the set of objects not at the database level, but at the application level using LINQ To Objects, there were also problems related only to the Entity Framework.
')
I confess, I can not classify myself as an active user and Entity Framework experts, because in practical life for many years I use another ORM. But this is also good, because I can tell you, dear readers, about the strangeness of the Entity Framework and the accompanying tools seen by an outside observer. In total not to capture, but I will tell that I see, since I tried this Entity Framework.

Measure or horror design seven times


CodeFirst and DBFirst do not offer. And that's why. The answer here is both philosophical and practical.

CodeFirst. Of course, it provides the most advanced features in the Entity Framework and full control. Some are willing to argue for this approach to hoarseness and to fight. My argument is simple: as long as you have 5-10-15 entities, you can keep the structure of the system in your head. Use on health! When the bill goes to hundreds, you can no longer. It really is easy for me to go hundreds and it doesn’t fit in my head so much. In the heads of comrades, too, especially when you enter a new person into the project. Therefore, I need pictures. Compact, intuitive, comfortable. Moreover, the model should not be on one picture, but on several, in parts, chosen as something specific. Well, and further, something must somehow be generated from the pictures, but I will not write the same type code hundreds of times, and also not to make any mistakes.

Maybe DBFirst? No, it's not for me either. Inheritance, how will I do? And then, how will I think about applied through the specifics of the base? There and their data types. If I wrote, say, VARBINARY, I’ll have to remember in my head that somewhere this is a picture, and somewhere there is some kind of hash. Well, to document it separately, or to breed folklore with friends. Multiply that by more than 300 entities. No, this is inconvenient. No, I want to think in application types. And then, what will remain after me?

It remains ModelFirst. Here I can at least draw entities as close as I get to C # and use .NET types for fields. But even here the spreading rake is carefully placed:
  1. Designer. Why are connections on it jumping like horrible? And they are carried out according to a diagram, like Visual Studio, and not like me, and in some cases they are tied with knots? I want them to be nailed to the sheet and not moving anywhere without my knowledge, otherwise I cannot find them after the next opening of the diagram. It seems I understand why CodeFirst is so popular.
  2. How to just cut a model into several diagrams? Well, 300 entities on one diagram are inconvenient to me and, besides, I am tortured with a mouse in the sliders. Yes, and the designer is very slow with such quantities of entities. Of course, there is a function “Move to new diagram”, but how then, if necessary, can we combine the diagrams back, can we move some of the entities, use the same entities on several diagrams?
    Gurus say there is some way there is. It is from the area of ​​manual editing of the .diagram file. But it is necessary to know the highest intention. But I’m not a guru, I don’t have time to get enlightened, I need to type in the applied functionality.
  3. Here, for example, I drew a string field. In the database it is generated by default as nvarchar (max). Weighed to the engineer who invented this default a generous click. Only for this silence. From the heart. In the properties of the EntitySet field, you cannot change the type even for one case. And I generally want to change this default so that all string fields are generated by a different type of database. What to do? Edit edmx text editor for everyone, and then rake the next glitches?
  4. I still can't draw using my application types, the choice of types is limited in the designer. What should I do if I have invented my application type (or I already have one) in the design process and just want to explain the ORM, how it is mapped to the database, and how - to the C # code? Well, of course, when implementing a type, I will have to observe certain rules, for example, the presence of implicit conversions to the native type, or the implementation of the old IConvertible into something.
  5. What should I do if I want the C # code that matches the entities to contain something special? And it is proposed in this case to return to CodeFirst. Either welcome to the brave, new world of T4.
  6. The inheritance that was designed in the editor is not decoupled into a “human” TPC (a separate table for each class with all fields), but only as:
    - TPT (a separate table for each class only with its own fields + connection to the ancestor). Then, when sampling, you will be: JOINs for each inheritance relationship - there is a problem with performance and a headache when trying to form a constraint on the required SQL;
    - TPH (put all levels in one table, spitting on normalization and other pure ideals). I wonder how you can think of it at all.
    If you still want TPC - again see CodeFirst + ModelBinder. I take this opportunity to say hello to the engineer himself. Dump him fatty clicks on the number of application entities.

To understand how “convenient” to design, I advise you to talk with someone who is trying to adapt the Entity Framework design tools, say, to Oracle. Especially making changes to the existing model. I recommend to find for this purpose a person of the opposite sex. The contrast of worldviews will add sharpness to your discussion.

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


Well, let's see how it is programmed.
Let's start with a simple one: create and save something:
CDLIBEntities dbctx = new CDLIBEntities(); //Country ctr1 = dbctx.Country.Create(); Country ctr1 = new Country(); ctr1.primaryKey = Guid.NewGuid(); //, !       . ctr1.Name = "Greece"; //Publisher pblshr1 = dbctx.Publisher.Create(); Publisher pblshr1 = new Publisher(); pblshr1.primaryKey = Guid.NewGuid(); //, !   ... pblshr1.Name = "First Publisher"; pblshr1.Country = ctr1.primaryKey; //,   , ,        ,  EF ,     pblshr1.Country1 = ctr1; //dbctx.Publisher.Attach(pblshr1); //!        ,     ???   ,   . dbctx.Publisher.Add(pblshr1); // , Attach      ,    ,      Add,   Attach,    //  ,    -  ? dbctx.SaveChanges(); 


Here is amazing:
  1. You must manually initialize the primary keys. Wouldn't it be better to have some separately stated pattern on the generation of the primary key? Well, and management capabilities;
  2. To bind entities, you must not only install the navigation property, but also insert the foreign key correctly, otherwise the Entity Framework will infringe on the violation of referential integrity. It is not clear: if each entity is identified by a key, then the bundle through a public field should automatically mean a relational relationship. It's so easy, just to signify a public field, but nooo, come on, dear programmer, remember that we have primary keys and foreign keys, meaning sit as if there is nothing more to be done;
  3. Yes, there is an approach when keys for objects are created in the database themselves. On the one hand, why not, especially if the key is integer, but on the other hand, until I execute the save request, I have no opportunity to understand what the key will be and use it further. This is especially fun to handle when a constructed object is needed in memory and with a key, but whether its storage in the database is required is determined at the very end.
  4. Attaching or adding objects to the context is done only by calling a method on the collection of the corresponding type. Moreover, different methods are used: to add - Add, and to attach an existing one - Attach. What is so "comfortable"? Maybe in the question what, where and how to add, the Entity Framework together with .Net somehow without me will begin to figure out what type this entity is and what happened to it?
  5. Why does the context exist? To still throw the brain programmer different "interesting" puzzles? For example, slip from the context instances of any stubs, and not the essential classes themselves. Or, for example, what will happen if we try to connect objects taken from different contexts? That, probably, will be fun. So, we must remember that from what context I got.

Something a lot of body movements for such a simple task as constructing and linking entities with each other.

How to ask DBMS or "under-include"


Let's see what happens when the Entity Framework reads something from the database. This is also very interesting. How and when it builds and executes requests.

Look at edmx (piece) to present a little puzzle:

By the way, the function of exporting a model to a picture is implemented in such a way that it not only delivers a lot of fun and pleasant minutes to the developer, but also brightens his leisure time.
If you export a relatively large model, then the resolution of the image will be something like this:

In principle, I admit that there are people who can see it. Here in the title of the entity is written "BlueRay".

And then, you noticed that I had to specify the power of the links, “by putting it on” in the picture “by hand”, since are they not visible? I did it on purpose "terribly." Do you think the export procedure contains an error and the power just did not fall into the diagram? No, everything is simpler, there are no errors, the power is honestly exported with light-gray-white symbols on a white background (look carefully at the ends of the links). This was probably done in order to check the color resolution of the monitors, as well as to train the color perception of the developers.

We want to display the title of the disc, the name of the publisher and the name of the country. There is a temptation to solve the problem in the forehead. And what is convenient. We take a collection from the context, we pass through the links. All data is downloaded from the database somehow themselves. Yes, you can not think about it, everything works, the goal is achieved, what else is needed.
Having implemented a simple design, we look at what requests EF performs while doing this.
 CDLIBEntities dbctx = new CDLIBEntities(); dbctx.Database.Log = (s => { textBox1.AppendText(string.Format("{0}{1}", s, Environment.NewLine)); }); //  ,    EF DVD[] dvds = dbctx.DVD.ToArray(); // ,        //DVD[] dvds = dbctx.DVD.Include("Publisher1").Include(@"Publisher1.Country1").ToList().ToArray(); //       joins,         ?       for (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("" + Environment.NewLine); 


First, check "in the forehead," run: see, the Entity Framework performs as many as 5 requests, and each is also inside its connection.
Textbox output:
 Opened connection at 04/17/2015 11:01:19 +05: 00

 SELECT 
     [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 04/17/2015 11:01:20 +05: 00

 - Completed in 11 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:01:20 +05: 00

 Opened connection at 04/17/2015 11:01:20 +05: 00

 SELECT 
     [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 04/17/2015 11:01:20 +05: 00

 - Completed in 6 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:01:20 +05: 00

 Opened connection at 04/17/2015 11:01:20 +05: 00

 SELECT 
     [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 04/17/2015 11:01:21 +05: 00

 - Completed in 2 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:01:21 +05: 00

 Movie 3 Second Publisher USA
 Opened connection at 04/17/2015 11:01:21 +05: 00

 SELECT 
     [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 04/17/2015 11:01:21 +05: 00

 - Completed in 0 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:01:21 +05: 00

 Opened connection at 04/17/2015 11:01:21 +05: 00

 SELECT 
     [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 04/17/2015 11:01:21 +05: 00

 - Completed in 0 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:01:21 +05: 00

 Movie 0 First Publisher Greece
 Movie 1 Second Publisher USA
 Movie 4 First Publisher Greece
 Movie 2 First Publisher Greece
 OK


This is due to the fact that the EF works on the principle of “give what is enough for,” meaning the navigation properties. For this convenience of development comes a tough reckoning: in large and complex systems, you can't do this, because this leads to wasteful use of resources of both the DBMS and the application.

It’s good that EF has some means to control the download. Uncomment the Include. Praise the wisdom of EF! Now there is a JOIN for each Navigation and just one request.
Textbox output:
   Opened connection at 04/17/2015 11:03:44 +05: 00

 SELECT 
     1 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 04/17/2015 11:03:45 +05: 00

 - Completed in 16 ms with result: SqlDataReader



 Closed connection at 04/17/2015 11:03:45 +05: 00

 Movie 0 First Publisher Greece
 Movie 4 First Publisher Greece
 Movie 2 First Publisher Greece
 Movie 1 Second Publisher USA
 OK


All or nothing


But the trouble is: if we just write Include, ALL properties of both our own and related entities are read. This seems to be the key to understanding where memory and speed go. “Re-include”, so to speak. Calling Include in the forehead is horrible - this is scary, especially branching to a lot of navigation.

Let's try to do better. Solution: select a set of only the required fields from the entities and read only them. Such a set can be called a projection or a representation, to whom it will be convenient and convenient. It is good that the Entity Framework has such an opportunity, however, a curve.
Reading in projection:
 CDLIBEntities dbctx = new CDLIBEntities(); dbctx.Database.Log = (s => { textBox1.AppendText(s); }); // 1.    ( ,         ) 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.   ( ,    ,  ,  ) // 3.        (      ) 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. We declare an anonymous type and select only what we need;
  2. We write a special class with only the necessary properties and use it;
  3. We make the heirs from the necessary entities and fill in the sample.

What's so inconvenient: Are we going to write like this for each sample? Is it productive?

And then: our goal is not to read, but to WORK with entities further, i.e. write logic that, as a result, leads to a change in the values ​​of entity fields. It should be easy to do this: change the property value of the entity, and then save the context when necessary, like this:
 // ,    -  dvd_derived[0].Name = " "; dbctx.SaveChanges(); 

This was not the case: this object was not created from the context and its type EF is unknown. And how do we save the changes now?

The curvature is that it is completely incomprehensible how to save the changes without taking the object out of context FULLY, with all the fields.

What to do? Invent people! For example, they write classes for each case of projection and map them somehow later on to essential classes (understandable to the context) and back. It is also possible to have separate sets of entity classes with property sets, corresponding selections and tuning to the same tables. You can, you can somehow dodge. Must be an exciting activity. But the essence of this lesson does not fit in with the correct philosophy, which is that ORM and ORM, that the constructs in the programming language unambiguously represent the objective essence (OBJECTS). Not so much that the part is there, the part is here. Here we read like this, then here and there, mapping, then cutting, then sticking, then wrapping the fish. If I took some kind of entity in the code, I must be reinforced to be sure that there is everything I need, and there is nothing else in the code that is responsible for the same entity. Divide and conquer, nothing to blur the functionality. Otherwise, this is not an ORM, and so is a classic set of crutches.

Instead of conclusion


The purpose of using ORM is to focus the programmer on the solution of objective problems, without losing the productivity of both the programmer and the software he develops. Productivity should generally increase. In the courtyard of the 21st century, and the issue of data access has not been closed. In this form, it is all beautiful for school crafts for 10-20 entities, and certainly not suitable in any serious task.

In short, I do not understand. Well, for me, the Entity Framework is a curiosity about legacy projects, we use another product ourselves, not at all massively promoted, but how does the majority live? After all, all over the world people work and enjoy life, how do they write and give out products? Well, in general, sleep peacefully at night. After all, I just took it, and right everything falls apart in my hands. Either serious, big projects are written on something else (I wonder on what), or they write everything manually, which is hard to believe in. People, how you live, I do not understand ...

I have some more questions for the trailer. But it is better, probably, in the next article I will tell you which ORM seems convenient to me, “best practices,” as is now fashionable to express. BUT?

Source: https://habr.com/ru/post/262023/


All Articles