work unitofwork unit pattern onion framework ejemplo c# design-patterns architecture entity-framework-4.1 abstraction

c# - unitofwork - unit of work entity framework core



¿Envolviendo DbSet<TEntity> con un DbSet/IDbSet personalizado? (1)

En primer lugar, creo que esto es algo ridículo, pero los otros miembros de mi equipo insisten en ello y no puedo encontrar un buen argumento en contra de eso, aparte de "creo que es tonto" ...

Lo que estamos tratando de hacer es crear una capa de datos completamente abstracta y luego tener varias implementaciones de esa capa de datos. Bastante simple, ¿verdad? Entrar Entity Framework 4.1 ...

Nuestro objetivo final aquí es que los programadores (hago todo lo posible para permanecer solo en la capa de datos) nunca quieren tener que estar expuestos a las clases concretas. Solo quieren tener que usar interfaces en su código, aparte de la necesidad evidente de crear una instancia de la fábrica.

Quiero lograr algo como lo siguiente:

Primero tenemos nuestra biblioteca "Común" de todas las interfaces, la llamaremos "Datos.comunes":

public interface IEntity { int ID { get; set; } } public interface IUser : IEntity { int AccountID { get; set; } string Username { get; set; } string EmailAddress { get; set; } IAccount Account { get; set; } } public interface IAccount : IEntity { string FirstName { get; set; } string LastName { get; set; } DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]? } public interface IEntityFactory { DbSet<IUser> Users { get; } DbSet<IAccount> Accounts { get; } }

A partir de eso tenemos una biblioteca de implementación, la llamaremos "Something.Data.Imp":

internal class User : IUser { public int ID { get; set; } public string Username { get; set; } public string EmailAddress { get; set; } public IAccount Account { get; set; } public class Configuration : EntityTypeConfiguration<User> { public Configuration() : base() { ... } } } internal class Account : IAccount { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]? public class Configuration : EntityTypeConfiguration<Account> { public Configuration() : base() { ... } } }

Fábrica:

public class ImplEntityFactory : IEntityFactory { private ImplEntityFactory(string connectionString) { this.dataContext = new MyEfDbContext(connectionString); } private MyEfDbContext dataContext; public static ImplEntityFactory Instance(string connectionString) { if(ImplEntityFactory._instance == null) ImplEntityFactory._instance = new ImplEntityFactory(connectionString); return ImplEntityFactory._instance; } private static ImplEntityFactory _instance; public DbSet<IUser> Users // OR IDbSet<IUser> OR [IDbSet implementation]? { get { return dataContext.Users; } } public DbSet<IAccount> Accounts // OR IDbSet<IUser> OR [IDbSet implementation]? { get { return dataContext.Accounts; } } }

Contexto:

public class MyEfDataContext : DbContext { public MyEfDataContext(string connectionString) : base(connectionString) { Database.SetInitializer<MyEfDataContext>(null); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new User.Configuration()); modelBuilder.Configurations.Add(new Account.Configuration()); base.OnModelCreating(modelBuilder); } public DbSet<User> Users { get; set; } public DbSet<Account> Accounts { get; set; } }

Entonces los programadores de front-end lo usarían como:

public class UsingIt { public static void Main(string[] args) { IEntityFactory factory = new ImplEntityFactory("SQLConnectionString"); IUser user = factory.Users.Find(5); IAccount usersAccount = user.Account; IAccount account = factory.Accounts.Find(3); Console.Write(account.Users.Count()); } }

Así que eso es todo ... Espero que alguien aquí pueda ser capaz de señalarme en la dirección correcta o de ayudarme con un buen argumento que puedo devolverle al equipo de desarrollo. He visto algunos otros artículos en este sitio sobre EF que no puede trabajar con interfaces y una respuesta que dice que no puede implementar IDbSet (lo cual me parece curioso, ¿por qué lo proporcionarían si no pudiera implementarlo?) es?) pero hasta ahora en vano.

¡Gracias de antemano por cualquier ayuda! J


El primer argumento es que EF no funciona con interfaces. DbSet debe definirse con una implementación de entidad real.

El segundo argumento es que sus entidades no deben contener DbSet , es decir, una clase relacionada con el contexto, y sus entidades deben estar DbSet de dicha dependencia a menos que vaya a implementar el patrón de registro activo. Incluso en tal caso, definitivamente no tendrá acceso a DbSet de una entidad diferente en otra entidad. Incluso si ajusta el conjunto, todavía está demasiado cerca de EF y la entidad nunca tiene propiedades que accedan a todas las entidades de otro tipo de entidad (no solo las relacionadas con la instancia actual).

Solo para dejarlo claro, DbSet en EF tiene un significado muy especial, no es una colección. Es el punto de entrada a la base de datos (por ejemplo, cada consulta LINQ en la base de datos de aciertos de DbSet ) y se encuentra en escenarios normales no expuestos en las entidades.

El tercer argumento es que está utilizando un solo contexto por aplicación: tiene una única instancia privada por fábrica de singleton. A menos que esté haciendo alguna aplicación de lote de ejecución única , es definitivamente incorrecto .

El último argumento es simplemente práctico. Se le paga por entregar características, no por perder tiempo en abstracción, lo que no le da a usted (ni a su cliente) ningún valor comercial. No se trata de demostrar por qué no debes crear esta abstracción. Se trata de probar por qué debes hacerlo. ¿Qué valor obtendrás al usarlo? Si sus colegas no pueden presentar argumentos que tengan valor comercial, simplemente puede dirigirse a su gerente de producto y dejar que use su poder: él tiene el presupuesto.

En general, la abstracción es parte de una aplicación orientada a objetos bien diseñada, eso es correcto. PERO:

  • Cada abstracción hará que su aplicación sea algo más compleja y aumentará el costo y el tiempo de desarrollo.
  • No todas las abstracciones harán que su aplicación sea mejor o más fácil de mantener: demasiada abstracción tiene efecto inverso
  • Resumir EF es difícil. Decir que abstraerá el acceso a los datos de la manera en que puede reemplazarlo con otra implementación es tarea de los gurús de acceso a los datos. En primer lugar, debe tener una muy buena experiencia con muchas tecnologías de acceso a datos para poder definir dicha abstracción que funcionará con todas ellas (y al final solo puede decir que su abstracción funciona con tecnologías en las que pensó cuando diseñó eso). ). Su abstracción funcionará solo con EF DbContext API y con nada más porque no es una abstracción. Si desea crear una abstracción universal, debe comenzar a estudiar el patrón de Repositorio, el patrón de Unidad de trabajo y el patrón de Especificación, pero eso es un gran trabajo para hacerlos e implementarlos de manera universal. El primer paso necesario es ocultar todo lo relacionado con el acceso a los datos detrás de esa abstracción, ¡incluyendo LINQ!
  • El resumen del acceso a los datos para admitir múltiples API solo tiene sentido si lo necesita ahora. Si solo piensa que puede ser útil en el futuro de lo que es en proyectos impulsados ​​por el negocio, la decisión completamente equivocada y el desarrollador que vino con esa idea no es competente para tomar decisiones de orientación comercial.

¿Cuándo tiene sentido hacer "mucha" abstracción?

  • Usted tiene ese requisito ahora, que traslada la carga de dicha decisión a la persona responsable del presupuesto / alcance del proyecto / requisitos, etc.
  • Necesitas abstracción ahora para simplificar el diseño o resolver un problema.
  • Usted está haciendo un proyecto de código abierto o pasatiempo y no está impulsado por las necesidades del negocio, sino por la pureza y la calidad de su proyecto.
  • Está trabajando en una plataforma (producto de venta al por menor de larga duración que vivirá durante mucho tiempo) o en un marco público: esto generalmente vuelve al primer punto porque este tipo de productos generalmente tiene tal abstracción como requisito

Si está trabajando solo en aplicaciones dirigidas (en su mayoría aplicaciones de propósito único bajo demanda o soluciones subcontratadas), la abstracción debe usarse solo si es necesario. Estas aplicaciones están impulsadas por los costos: el objetivo es ofrecer una solución de trabajo a un costo mínimo y en el menor tiempo. Este objetivo debe alcanzarse incluso si la aplicación resultante no es muy buena internamente, lo único que importa es si la aplicación cumple con los requisitos. Cualquier abstracción basada en "qué pasaría si ... sucede" o "tal vez necesitaremos ..." aumenta los costos por requisitos virtuales (no existentes) que en un 99% nunca ocurrirán y en la mayoría de los casos el contrato inicial con el cliente no contó que tales costos adicionales.

Por cierto Este tipo de aplicaciones está orientado a las API de MS y la estrategia del diseñador: MS creará muchos diseñadores y generadores de código que crearán soluciones no óptimas, pero baratas y rápidas, que pueden crear personas con un conjunto de habilidades más pequeño y son muy baratas. El último ejemplo es LightSwitch.