Never
Simple as that. Implementing one repository on top of another doesn’t make any sense. This is easy to see by analyzing how the DbSet
.
DbSet
implements IEnumerable
and IQueryable
(because IQueryable
implements IEnumerable
). Calling extension methods that return IQueryable
, return is an object that accumulates filters. For example:
var query = db.Entidades.Where(x => x.EntidadeId > 25);
Here I’m not executing anything. I’m just getting an object that, in its resolution for an enumeration, will return me objects whose value of EntidadeId
is greater than 25. I can accumulate predicates without executing the code. Suppose the object query
from the previous example, I can do the following:
query = query.OrderBy(x => x.Nome);
In the implementations I see around, all methods solve the return to IEnumerable
, which forces the database to execute the enumeration resolution (hence SQL), accumulating superfluous results in memory and increasing the search execution time because the result is unnecessarily higher.
Below I explain the great fallacies of who defends repository on top of Entity Framework, as well as give examples of the above to make the explanation clearer.
Fallacy 1: "It is more practical to use."
I don’t see any practicality in it:
public IEnumerable<Model.Article> GetArticles()
{
return _context.Articles;
}
The example speaks for itself. Articles
is a DbContext
, implementing IQueryable
(implementing IEnumerable
). On returning IEnumerable
, we are forcing the conversion of IQueryable
in IEnumerable
, i.e., executing the entire query (as a TABLE SCAN
, in fact, because there are no filter predicates defined).
Besides being a useless encapsulation, forces the bank to work wrong, makes the application work wrong and accumulates memory for operations that do not need.
Fallacy 2: "The idea is not to repeat highly complex logics several times, in favor of DRY"
If the complex logic is repeated several times, the solution is then to standardize 100% of the system (which is possibly 20% or less which is really complex) or only the 20% complex?
How about Extensions to build the research?
public static class MinhaPesquisaComplexaExtensions
{
public static IQueryable<MinhaEntidade> MinhaCondicaoComplexa(this IQueryable<MinhaEntidade> conjunto)
{
return conjunto.Where(/* Coloque aqui a condição complexa */);
}
}
Use:
var retornoComplexo = context.Entidades.MinhaCondicaoComplexa().Where(/* Sim, posso filtrar mais */).ToList();
Remembering I don’t need to wear .ToList()
until such time as I need to resolve the enumeration. That is, the query can be executed only at the last moment, taking full advantage of the lazy behavior implemented in the framework.
Fallacy 3: "It is easier to change the ORM."
This one I really like. The easiest is to refactor context statements, for example, this one from an application that uses an Identity context:
private ApplicationDbContext db = new ApplicationDbContext();
For:
private IApplicationDbContext db = new ApplicationDbContext();
IApplicationDbContext
would be to extract the interface from its context (which is a repository) so yes we can implement a repository on top of this new interface, ie:
public class MeuContextoDapper: IApplicationDbContext
{ ... }
Then you use the repository you want.
Obviously, the DbSet
s need to be rewritten. I do not recommend using only IDbSet
because some extension methods only exist in DbSet
. Better extract the interface from DbSet
ensuring the methods of extension.
Fallacy 4: "EF encapsulates repository abstraction and infrastructure, so it takes an extra layer to separate business logic from infrastructure."
Wrong. Note that the technological implementation is separate from the abstraction of the repository since version 6.
Furthermore, if this were the case, it would not be possible, for example, to change the Connection Factory. See some examples:
SQL Server
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
SQL Server Localdb
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
Mysql
<entityFramework>
<defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6"></defaultConnectionFactory>
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6"></provider>
</providers>
</entityFramework>
Oracle
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="Oracle.ManagedDataAccess.Client" type="Oracle.ManagedDataAccess.EntityFramework.EFOracleProviderServices, Oracle.ManagedDataAccess.EntityFramework, Version=6.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
</providers>
</entityFramework>
Firebird
<entityFramework>
<defaultConnectionFactory type="FirebirdSql.Data.EntityFramework6.FbConnectionFactory, EntityFramework.Firebird" />
<providers>
<provider invariantName="FirebirdSql.Data.FirebirdClient" type="FirebirdSql.Data.EntityFramework6.FbProviderServices, EntityFramework.Firebird" />
</providers>
</entityFramework>
Postgresql
<entityFramework>
<defaultConnectionFactory type="Npgsql.NpgsqlFactory, Npgsql" />
<providers>
<provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, Npgsql.EntityFramework" />
</providers>
<entityFramework>
Fallacy 5: "Not using repositories with Entity Framework causes high coupling and hurts the principles of SOLID and good OO programming".
Let’s review the SOLID:
- S - Single Responsibility Principle, Principle of Single Liability: A class should have a single responsibility, not a Swiss army knife with thousands of functions;
- The - Open Closed Principle, Open/Closed Principle: In inheritance, derived classes should avoid having peculiar methods and elements when public. Better alternatives are polyformism, the use of interfaces (contracts) and extensions (Extensions, when there is);
- L - Liskov Substitution Principle, Principle of Liskov’s Substitution: If we have two classes, father and daughter, then the parent class objects in a program can be replaced by the daughter class objects without having to change the properties of this program;
- I - Interface Segregation Principle, Principle of Interface Segregation: An interface should not be fat, that is, the objects that implement it should not need to implement a lot of methods that will mostly not be used;
- D - Dependency Inversion Principle, Principle of Inversion of Dependence: Abstractions should not depend on details. Details should depend on abstractions, i.e., if a class or functionality is used in many places with diverse class possibilities, it should be replaced by a generic term or an interface.
In short, this statement does not make much sense.
Here it should be noted that the implementation of a repository on top of the Entity Framework hurts, first, the Principle of Interface Segregation, because CRUD operations are implemented by methods that already exist:
- Select everything:
ToList()
, AsEnumerable()
;
- Select a:
First()
, FirstOrDefault()
, Single()
, SingleOrDefault()
;
- Any other selection:
Where()
followed by ToList()
or AsEnumerable
;
- Insert:
Add()
;
- Edit:
Entry(objeto).State = EntityState.Modified
;
- Rule out:
Remove()
.
Over high coupling, the only coupling that exists is between the context and its domain classes, which justly were made to shape the behavior of the context. That is, even if the coupling is reduced using interfaces, this generation of interfaces is useless because Models only make sense when used together with the context.
On the principle of Liskov’s replacement, a simple interface extraction of DbSet
shows that we can implement a repository the way we want without prejudice to functionalities. There are other losses such as loss of SQL generation by filter extension methods, but implementation is possible. There is only one caveat here: Microsoft itself has not respected this principle because DbSet
has a much richer implementation than IDbSet
in the matter of methods.
On the Open/Closed Principle, Entity Framework supports inheritance and composition. Almost all methods that interact with DbSet
and DbContext
are extensions, so replacing them is very simple by simply extracting the interface and rewriting the database search method.
On the Principle of Single Liability, DbContext
is an observer of DbSets
, and just that, and DbSet
is a collection of records of a particular entity with the 4 basic operations of a database: select, insert, edit and delete.
On the Dependency Inversion Principle, this can be solved by injection of dependency, but only if the programming team makes much question, because a DbContext
shall exist only during the cycle of a request and therefore instantiated with it and deleted with it.
In conclusion, the Entity Framework does not hurt SOLID, with the exception of DbSet
. Does not hurt OO principles. Does not cause high coupling.
Classic Problems of Approach
Now that I have talked about the fallacies, I will talk about the problems that the use of repositories brings.
Paging
The answer below summarizes.
Pagination C# MVC Asp . NET
Anticipated Charge (Eager Load)
The lazy load (Lazy Load) is set by default in the Entity Framework. What happens is that some turn off the lazy load to define the load strategy in the repository:
public MeuContexto() : base("name=DefaultConnection")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
I don’t know the meaning of this. The correct thing would be for the programmer to say when the anticipated load will be used. This is done through the extension method Include
or by native method Include
.
The big difference here is that Include
, in an object context IQueryable
, is pre-compiled by Linq to generate an SQL sentence with JOIN
, and not simply use an operation in memory.
Problem of the Highlighted Context
Assuming an application that uses a repository on top of Entity Framework and has a code like this:
var entidadeDependente = EntidadesDependentesRepositorio.Selecionar(...);
var entidade = EntidadesRepositorio.Selecionar(...);
entidade.EntidadesDependentes.Add(entidadeDependente);
EntidadesRepositorio.Salvar(entidade);
The most common problem is:
An Entity Object cannot be referenced by Multiple instances of Ientitychangetracker.
It’s called Problem of the Highlighted Context because two objects are being observed by different contexts, and the correct thing is to have only one data context per request. That is, the object entidadeDependente
is in another context, detached from the context of the main object, entidade
.
It’s just another example of the misuse of the Entity Framework, which acts as an object observer, monitoring its modifications and persisting when SaveChanges()
is called.
The following survey has some results on this problem. There is also this research, where the problem is dealt with by other names.
Problem of Conflicting Objects
It is characterized by the following message:
Attaching an Entity of type 'Classetal' failed because Another Entity of the same type already has the same Primary key value. This can happen when using the 'Attach' method or Setting the state of an Entity to 'Unchanged' or 'Modified' if any entities in the Graph have Conflicting key values. This may be because some entities are new and have not yet Received database-generated key values. In this case use the 'Add' method or the 'Added' Entity state to track the Graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
Very often in projects that try to implement the DDD. It occurs when trying to attach an object that already exists to the context. A classic example of error is:
var entry = contexto.Entry(objeto); // Isto já faz o contexto observar o objeto.
contexto.Entidades.Attach(objeto); // O erro ocorre aqui.
It can happen with the same object (asking the context to observe it twice) or with different objects, when they have the same primary key.
This answer speaks of situations where repositories add value: http://answall.com/a/51439
– Caffé
What is Hungarian Notation? kk
– Rod
@Rod, do you know the habit of putting an object type abbreviation in the variable name? type
var strNome = "Toby Mosque"
, this is what some understand by Notation Hungara... whereas the current intention was to use an abbreviation of the data type in the variable, as for example a certain datums
or unsafeu
, as in the example:var sValue = 515; //endereço seguro em memoria
andvar uValue = 6874168416; //endereço não seguro
... In any case this habit is unnecessary for modern languages, being useful only for lower level languages.– Tobias Mesquita
@Tobymosque understood, wow, never crossed my mind to use that kkkk nor from the time of the vb6 did not use that hauhauha
– Rod
@Rod, but surely you’ve seen someone using it, probably without understanding why.
– Tobias Mesquita
@Tobymosque truth...vi até em Procedure sql hauhauha
– Rod
sp_as a prefix to
stored procedure
in itself is already polemic.– Tobias Mesquita