Decía el otro día que me había puesto a ampliar mi mapeo de SAP Business One en Entity Framework clásico para hacerlo también en Entity Framework Core 7 y .Net 6.0.
Tuve que empezar creando unos métodos de extensión para tener la misma funcionalidad que en EF clásico.
Luego, dividí la clase del DbContext en tres archivos:
- Uno para la declaración de los DbSet.
- Otro con constructores y configuración para EF clásico.
- Y el tercero, para los constructores y configuración para EF Core.
El paso que me quedaba era revisar las clases de configuración de las entidades.
Clases de configuración
Recordemos que, tanto en EF clásico como en Core, la configuración mediante Fluent Api se hace en el OnModelCreating del DbContext y que podíamos poner el código de configuración directamente en el método o utilizar clases de configuración. Con un proyecto que empezó por cerca de 100 entidades (y que ya supera las 150), la única forma de tener un OnModelCreating legible era utilizar clases de configuración. En la anterior entrada vimos cómo se agregaban al modelBuilder en cada caso. Hoy, vamos con las propias clases.
La estructura para EF clásico y Core es distinta.
- En EF clásico heredamos de EntityTypeConfiguration(Of TEntity). El código de configuración va en el constructor de la clase.
- En EF Core, implementamos la interfaz IEntityTypeConfiguration(Of TEntity). El código de configuración va en el método Configure definido por la interfaz.
Bueno, toca algo de compilación condicional. Lo importante es, ¿hay que repetir el código Fluent Api o nos vale entre ambos?
No puedo hablar por todos los casos, porque tampoco tengo mucha configuración hecha. La base de datos ya existía, así que lo único que hice fue definir las relaciones entre entidades. Y aquí pinchamos en hueso. En EF clásico, una relación 1 a n se declaraba de forma distinta a una de 0 a n. En el primer caso, se usaba la instrucción WithRequired y en el segundo, WithOptional. Sin embargo, en Core se usa para ambos casos la misma instrucción, WithOne. Así que toca hacer el camino difícil:
Friend Class AlbaranVentasLineaConfigurador #If NET6_0_OR_GREATER Then Implements IEntityTypeConfiguration(Of AlbaranVentasLinea) Public Sub Configure(builder As EntityTypeBuilder(Of AlbaranVentasLinea)) _ Implements IEntityTypeConfiguration(Of AlbaranVentasLinea).Configure With builder .HasKey(Function(e) New With {e.DocEntry, e.LineNum}) .HasMany(Function(e) e.Portes) _ .WithOne(Function(e) e.Linea) _ .HasForeignKey(Function(e) New With {e.DocEntry, e.LineNum}) End With End Sub #Else Inherits EntityTypeConfiguration(Of AlbaranVentasLinea) <DebuggerStepThrough> Public Sub New() Me.HasKey(Function(e) New With {e.DocEntry, e.LineNum}) Me.HasMany(Function(e) e.Portes) _ .WithRequired(Function(e) e.Linea) _ .HasForeignKey(Function(e) New With {e.DocEntry, e.LineNum}) End Sub #End If End Class
Data Annotations
Con las Data Annotations no esperaba encontrar problema, pero uno me estalló en la cara: la forma de declarar una clave primaria múltiple en EF clásico no es válida en EF Core. En EF clásico, marcábamos las propiedades que eran clave primaria con el atributo Key y con el atributo Column indicábamos el orden (<Column(Order:=0, 1, 2…):
<Key> <Column(Order:=0)> Public Property DocEntry As Integer <Key> <Column(Order:=1)> Public Property LineNum As Integer
En EF Core, Key nos vale para clave primara simple, pero para las múltiples se han inventado un nuevo atributo que se le pone a la entidad (eso en la versión 7, antes no se podía hacer de ninguna forma):
<Table("DLN1")> <PrimaryKey("DocEntry", "LineNum")> Public Class AlbaranVentasLinea (...)
En todo caso, son dos formas distintas, así que me ha tocado retirar los atributos y declararlo por Fluent Api (Me.HasKey…, se puede ver en el fragmento anterior).
Más métodos de extensión
Con todo esto, ya pude compilar y probar mi nueva dll con LinqPad, tanto para EF clásico como para Core. Una vez arreglados los errores tontos por abuso del copypaste, me ha quedado un proyecto muy resultón.
El paso siguiente fue preparar un proyecto que utilizara la biblioteca y que compilara tanto para .Net Framework con EF clásico como para .Net y EF Core. Ahí me encontré con un par de métodos para ejecutar consultas SQL que tenía en EF clásico en EF Core existen, pero con otro nombre. Hablo de ExecuteSqlCommand, que ahora se llama ExecuteSqlQuery, y de SqlQuery (para DbSet), que ahora tiene el nombre de FromSql. Para quitarme líos, me hice unos métodos de extensión para poder tener el viejo nombre:
<System.Runtime.CompilerServices.Extension> Public Function ExecuteSqlCommand(database As Infrastructure.DatabaseFacade, query As FormattableString) As Integer Return database.ExecuteSql(query) End Function <System.Runtime.CompilerServices.Extension()> Public Function SqlQuery(Of TEntity As Class)(source As DbSet(Of TEntity), query As FormattableString) As IQueryable(Of TEntity) Return source.FromSql(query) End Function
Con esto, completé el proyecto y ya tengo las bibliotecas operativas. Ahora me queda ponerme a aprender Blazor para poder usarlas.