De EF clásico a EF Core I — El Context

Tengo un mapeo de parte de la base de datos de SAP Business One en Entity Framework desde hace unos años que ya pasa de las 150 entidades. Hace no mucho modifiqué el tipo de proyecto de Visual Studio, del clásico de .Net Framework al nuevo estilo usado en .Net Core. El motivo era que este nuevo tipo permitía compilar con facilidad para distintas versiones de .Net. Este otoño, aprovechando esto, me planteé añadir .Net 6.0 y el nuevo EF Core 7 como plataformas de destino.

Ya comenté ayer los problemas que me encontré en las primeras pruebas. Solventados (o trampeados) éstos, pasé al plato principal.

El Context

La parte buena es que la declaración de los DbSet es idéntica, por lo que ahí no había que tocar nada, salvo en la parte de los imports (algo que, por otra parte, tendré que hacer en todo el proyecto una y otra vez):

#If NET6_0_OR_GREATER Then
Imports Microsoft.EntityFrameworkCore
#Else
Imports System.Data.Entity
#End If

Los constructores cambian. En EF Core está preparado para inyección de dependencias. Así que, mientras que en EF clásico preparábamos la cadena de conexión en el constructor:

Public Sub New(DataBaseServer As String, DataBase As String, User As String, Pass As String, Logger As Action(Of String))
    MyBase.New($"data source={DataBaseServer};initial catalog={DataBase};persist security info=True;user id={User};password={Pass};MultipleActiveResultSets=True;App=EntityFramework")
    Me.Database.Log = Logger
#If DEBUG Then
    If Me.Database.Log Is Nothing Then
        Me.Database.Log = AddressOf Debug.Write
    End If
#End If
End Sub

En EF Core lo haremos sobreescribiendo el método OnConfiguring. La estructura de la cadena de conexión también es distinta:

Protected Overrides Sub OnConfiguring(optionsBuilder As DbContextOptionsBuilder)
    If Not optionsBuilder.IsConfigured Then
        optionsBuilder.UseSqlServer($"server={_DBOServer};database={_DBO};user id={_DBUser};password={_DbUserPassword};Encrypt=False;")
    End If
End Sub

Hasta ahora, ha sido sencillo. Vamos con la configuración.

En ambos, se hace sobreescribiendo el método OnModelCreating. El parámetro que reciben es distinto, un DBModelBuilder en EF clásico, un ModelBuilder en EF Core. Dejando eso a un lado, la estructura parece similar. Igual nos escapamos definiendo una compilación condicional de la declaración del OnModelCreating.

Pues no. Los desarrolladores de EF Core se han esforzado en hacer las cosas «bien» desde el principio, sin pensar mucho en ponernos fácil las migraciones de proyectos. En EF clásico, para añadir una clase de configuración al Context, lo hacíamos así:

modelBuilder.Configurations.Add(New ArticuloConfigurador)

Bastante intuitivo. A la colección de configuraciones del DBModelBuilder le añadíamos una nueva.

Sin embargo, en EF Core no añadimos, sino que aplicamos:

modelBuilder.ApplyConfiguration(New ArticuloConfigurador)

Sencillamente genial. Mis planes de hacer algo indoloro se acababan de ir al traste. Esto me obligaba a hacer una codificación totalmente distinta para los respectivos OnModelCreating, por lo que dividí el Context en tres clases parciales: una común, con los DbSet, una con los constructores y métodos de configuración para EF clásico y la tercera, el equivalente para EF Core.

Seguimos: en EF clásico, si hacíamos una aproximación de base de datos primero, había que especificarle al context «oye, no toques la base de datos»:

Database.SetInitializer(Of SAPContext)(Nothing) ' No le dejamos tocar la BDD.

En EF Core esto no nos hace falta, algo que nos ahorramos.

Luego tenemos las convenciones. La principal que me atañe es la del tipo decimal, que en EF tiene una precisión de (18,2), pero en SAP Business One es (19,6). Esto, en EF clásico se solucionaba también en el OnModelCreating:

modelBuilder.Conventions.Remove(Of DecimalPropertyConvention)()
modelBuilder.Conventions.Add(New DecimalPropertyConvention(19, 6))

En EF Core se han llevado esto a otro método, ConfigureConventions, que debemos sobreescribir para la ocasión:

Protected Overrides Sub ConfigureConventions(configurationBuilder As ModelConfigurationBuilder)
    MyBase.ConfigureConventions(configurationBuilder)
    configurationBuilder.Properties(Of Decimal).HavePrecision(19, 6)
End Sub

Por último, en EF Core tenemos algo que en EF clásico ni lo olíamos: filtros globales. Esto es importante en una base de datos como la de SAP, donde, por ejemplo, en la tabla de artículos no sólo hay artículos. También tenemos activos fijos, por ejemplo, y en este proyecto no me interesan. Para EF clásico utilicé este proyecto que, sin embargo, nunca me funcionó bien del todo. En EF Core es tan simple como añadir lo siguiente al OnModelCreating:

modelBuilder.Entity(Of Articulo).HasQueryFilter(Function(e) e.Tipo = ITM_TIPOARTICULO_ARTICULOS)

Me queda un cuarto archivo de clase parcial, que ya tenía de antes: uno con funciones. Algunas tan simples como mapear una función de SQL, otras incluyen cálculos internos. Aquí no tengo que tocar nada (salvo la parte de los Imports) porque ya me encargué de ello el otro día:

Public Function GetTipoProduccionLote(DocEntry As Integer) As ELoteTipoProduccion
    Dim mResult = Me.Database.SqlQuery(Of Integer)($"Select dbo.YNECAM_WORTipoLote({DocEntry})").Single
    Dim mResultado = CType(mResult, ELoteTipoProduccion)
    Return mResultado
End Function

Con esto, el Context está preparado para compilar tanto para EF Core 7 y .Net 6.0 como para EF clásico. El siguiente paso es más largo y aburrido: revisar las clases de configuración y las entidades. Todas.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.