Issues with Entity Framework on Blazor Server

Blazor Server is a technology for easily writing Web systems on the .Net platform. For convenient work with databases, the Entity Framework library was created, which allows the programmer to work directly with models without thinking about SQL queries. But is everything so good if you combine Blazor and EF?

Problem #1 – two threads can’t work on the same DbContext at the same time

A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.

What does it mean?

In order to understand why such an error occurs, you need to know one thing – DbContext is not Thread-save, that is, you cannot simultaneously use the same context in two different threads (From official Microsoft documentation). In Blazor, this error often occurs due to the fact that several components that during initialization, one DbContext is accessed, created simultaneously.

Example

Imagine that you need to write a page on which the user must indicate his educational institution (he needs to select the country, city and educational institution itself), as well as the education that he has already received (direction and specialty). All information that the user selects is stored in the database. In order to make the structure of your page more understandable, you divide it into two components – one deals with location, the other with education. To do this, you get a DbContext in each component using the standard Blazor Dependency Injection system, then override the OnInitialized (or OnInitializedAsync) method, where you use the received context to get the information you need from the database. As a result, after launching the application and opening this page, the child components are created and work with the database at the same time and you get the above error.

How to decide?

There are several solutions:

  1. Get all the necessary information from the database in the parent component and pass it to the child as a parameter. In addition, you need to make sure that the user will not be able to simultaneously run methods that will work with the database – for this you can use the IsLoading bool variable, binding it to all child components.
    The disadvantage of this method is the complication of the page structure and the inability to perform operations with the database asynchronously

  2. Create a new DbContext for each method or component that can be executed asynchronously. This functionality can be implemented using DbContextFactory or IServiceScopeFactory.
    The disadvantage of this method is the increased consumption of resources.

Outcome

The first solution is suitable only for those cases when you do not need to perform any operations in the database asynchronously. Therefore, for all other situations, you need to use the second solution.

Issue #2 – Unable to track an instance of an object because an object with the same Id already exists

The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked

What does it mean?

This means you are in big trouble. The fact is that in order for DbContext to track model changes, it has a list of TrackedEntities (From official Microsoft documentation). This list is initially empty. But after that it will be filled with models that interacted with this context. If in this listthe model you need will be and you create it copythen after interactions of this copy with DbContextthis context will not be able to add it to itself in the TrackedEntities list, which will cause an exception.

Example

Let’s use the previous example. Suppose you still decide to choose the second solution, namely to use IServiceScopeFactory and for each method create a new DbContext. After the user specifies all the data he has selected, you need to save them to the database (let’s call the model where this information will be stored as UserInformation). In the component where the user indicates his education, you have created such a mechanism – first the user selects the direction, and after selection, specialties for the selected direction are pulled from the database. But you made a mistake, due to which, when the component is initialized, a list of directions is drawn, each of which already has a list of specializations. Thus, this is the kind of incident – specialties that are tied to the direction, and specialties that were obtained separately – although they are the same models in the database, they will be stored in memory as different objects (they were pulled from the database using different DbContext). Then, if you try to store the selected direction and the selected specialization in the UserInformation model, you will store an associated direction with a list of specializations and the selected specialization there (Important: the model of the chosen specialization in UserInformation and the same model in the list of specializations of the chosen direction is two different objects). That is, when you save UserInformation to the database via Insert / Update in a new DbContext, the context will try to keep track of all nested models. Suppose, at first, he will track the chosen direction with specializations attached to it. Then he will try to do the same with the chosen specialization, but he will find that he is already tracking such a model, and it is not clear what to do with his copy. That is why you will get the above error.

How to decide?

  1. Use the approach with DTO models. Thus, you are not attached to objects from the database, but to their copies. After changing the copies, you again get the models you need from the database and transfer the data from the copies to them.

    The disadvantage of this method is a more complex project structure, increased resource consumption.

  2. Do not allow copies. If you attach a model to the context that you received from another context, it will no longer use the new object, but the one that you attached to it.

    The disadvantage of this method is that you will not always be able to track the repetition (especially if you have a complex structure of models and the logic of interaction with them), which means that an error may occur on sale at the most inopportune moment.

  3. Do not use different contexts (the first version of the first problem).

Outcome

If you don’t have a big project, but the problem needs to be solved urgently, you can safely use the 2nd option. But if you know that your project is already big or will be big, then it is better to use the first option. Moreover, if you correctly modify the 3-layer project architecture for this solution, you will get a ready-made basis for the project API.

So what to use?

In fact, it all depends on the complexity of the project. But still, if you want to get rid of all these problems, use IServiceScopeFactory and DTO models.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *