c# - una - Acceso a las entidades de la base de datos desde el controlador
pasar datos de una vista a un controlador c# (9)
tl; dr
En un buen diseño. ¿Debería manejarse el acceso a la base de datos en una capa de lógica de negocios separada (en un modelo MVC de asp.net), o está bien pasar los objetos DbContext
s o DbContext
a un controlador?
¿Por qué? ¿Cuáles son los pros y los contras de cada uno?
Estoy creando una aplicación MVC de ASP.NET en C #. Utiliza EntityFramework como un ORM.
Vamos a simplificar un poco este escenario.
Tengo una tabla de base de datos con lindos gatitos esponjosos. Cada gatito tiene un enlace de imagen de gatito, índice de fluffiness de gatito, nombre de gatito e identificación de gatito. Estos mapean a un POCO generado por EF llamado Kitten
. Podría usar esta clase en otros proyectos y no solo en el proyecto MVC de asp.net.
Tengo un KittenController
que debería obtener los últimos gatitos suaves en /Kittens
. Puede contener cierta lógica al seleccionar el gatito, pero no demasiada lógica. He estado discutiendo con un amigo sobre cómo implementar esto, no revelaré los lados :)
Opción 1: db en el controlador:
public ActionResult Kittens() // some parameters might be here
{
using(var db = new KittenEntities()){ // db can also be injected,
var result = db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
return Json(result,JsonRequestBehavior.AllowGet);
}
}
Opción 2: modelo separado
public class Kitten{
public string Name {get; set; }
public string Url {get; set; }
private Kitten(){
_fluffiness = fluffinessIndex;
}
public static IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10){
using(var db = new KittenEntities()){ //connection can also be injected
return db.Kittens.Where(kitten=>kitten.fluffiness > 10)
.Select(entity=>new Kitten(entity.name,entity.imageUrl))
.Take(10).ToList();
}
} // it''s static for simplicity here, in fact it''s probably also an object method
// Also, in practice it might be a service in a services directory creating the
// Objects and fetching them from the DB, and just the kitten MVC _type_ here
}
//----Then the controller:
public ActionResult Kittens() // some parameters might be here
{
return Json(Kittens.GetLatestKittens(10),JsonRequestBehavior.AllowGet);
}
Notas: Es poco probable que GetLatestKittens
se use en otra parte del código, pero podría hacerlo. Es posible usar el constructor de Kitten
lugar de un método de construcción estática y cambiar la clase de Kittens. Básicamente, se supone que es una capa sobre las entidades de la base de datos, por lo que el controlador no tiene que estar al tanto de la base de datos real, el asignador o el marco de la entidad.
- ¿Cuáles son algunas ventajas y desventajas para cada diseño?
- ¿Hay un claro ganador? ¿Por qué?
Nota: Por supuesto, los enfoques alternativos también se valoran como respuestas.
Aclaración 1: Esta no es una aplicación trivial en la práctica. Esta es una aplicación con decenas de controladores y miles de líneas de código, y las entidades no solo se utilizan aquí sino en decenas de otros proyectos de C #. El ejemplo aquí es un caso de prueba reducido .
@Win tiene la idea de que más o menos sigo.
Tener la Presentation
solo presenta .
El controlador simplemente actúa como un puente , no hace nada en realidad, es el hombre medio. Debe ser fácil de probar.
El DAL es la parte más difícil. A algunos les gusta separarlo en un servicio web, lo he hecho para un proyecto una vez. De esa manera, también puede hacer que DAL actúe como una API para que otros (interna o externamente) la consuman, por lo que WCF o WebAPI vienen a la mente.
De esa manera, su DAL es completamente independiente de su servidor web. Si alguien hackea su servidor, el DAL probablemente todavía es seguro.
Depende de usted, supongo.
De cualquier manera no es tan bueno para la prueba. Use la inyección de dependencia para obtener el contenedor DI para crear el contexto db e inyectarlo en el constructor del controlador.
EDIT: un poco más en las pruebas
Si puede realizar una prueba, puede ver si su aplicación funciona según las especificaciones antes de publicar.
Si no puedes probar fácilmente no escribirás tu prueba.
desde esa sala de chat:
Bien, en una aplicación trivial la escribes y no cambia mucho, pero en una aplicación no trivial obtienes estas cosas desagradables llamadas dependencias, que cuando cambias una, rompe mucha mierda, así que usas la inyección de dependencia para inyecte un repositorio que pueda falsificar, y luego puede escribir pruebas unitarias para asegurarse de que su código no
El segundo enfoque es superior. Probemos una analogía coja:
Entras en una pizzería y caminas hacia el mostrador. "Bienvenido a McPizza Maestro Double Deluxe, ¿puedo tomar su pedido?" el cajero con granos le pregunta, el vacío en sus ojos amenaza con atraerlo. "Sí, tendré una pizza grande con aceitunas". "Está bien", responde el cajero y su voz ronca en medio del sonido "o". Grita hacia la cocina "¡Un Jimmy Carter!"
Y luego, después de esperar un poco, obtienes una pizza grande con aceitunas. ¿Notaste algo peculiar? El cajero no dijo: "¡Toma un poco de masa, dale vueltas como si fuera Navidad, vierte un poco de salsa de tomate y queso, esparce aceitunas y pones en un horno durante unos 8 minutos!" Ahora que lo pienso, eso no es nada peculiar. El cajero es simplemente una puerta de entrada entre dos mundos: el cliente que quiere la pizza y el cocinero que hace la pizza. Por lo que sabe el cajero, el cocinero obtiene su pizza de extraterrestres o los rebana de Jimmy Carter (él es un recurso cada vez más reducido, la gente).
Esa es tu situación. Tu cajero no es tonto. Él sabe cómo hacer pizza. Eso no significa que deba estar haciendo pizza, o decirle a alguien cómo hacer pizza. Ese es el trabajo del cocinero. Como lo ilustran otras respuestas (en particular las de Florian Margaine y Madara Uchiha), hay una separación de responsabilidades. El modelo podría no hacer mucho, podría ser solo una llamada de función, podría ser incluso una sola línea, pero eso no importa, porque al controlador no le importa .
Ahora, digamos que los propietarios deciden que las pizzas son solo una moda (¡una blasfemia!) Y que cambias a algo más contemporáneo, una elegante hamburguesa. Repasemos lo que pasa:
Entras en una hamburguesa elegante y caminas hacia el mostrador. "Bienvenido a Le Burger Maestro Double Deluxe, ¿puedo tomar su pedido?" "Sí, tendré una gran hamburguesa con aceitunas". "Está bien", y se dirige a la cocina, "¡Uno de Jimmy Carter!"
Y luego, obtienes una gran hamburguesa con aceitunas (ew).
Esta es la frase clave allí:
Podría usar esta clase en otros proyectos y no solo en el proyecto MVC de asp.net.
Un controlador está centrado en HTTP. Solo está ahí para manejar las solicitudes HTTP. Si desea utilizar su modelo en cualquier otro proyecto, es decir, su lógica empresarial, no puede tener ninguna lógica en los controladores. Debe poder quitarse su modelo, ponerlo en otro lugar, y toda la lógica de su negocio aún funciona.
Entonces, no, no acceda a su base de datos desde su controlador. Mata cualquier posible reutilización que puedas obtener.
¿Realmente desea volver a escribir todas sus solicitudes de db / linq en todos sus proyectos cuando puede tener métodos simples que puede reutilizar?
Otra cosa: su función en la opción 1 tiene dos responsabilidades: obtiene el resultado de un objeto mapeador y lo muestra. Eso es demasiadas responsabilidades. Hay un "y" en la lista de responsabilidades. Su opción 2 solo tiene una responsabilidad: ser el enlace entre el modelo y la vista.
Las opciones 1 y 2 son un poco extremas y me gusta la elección entre el diablo y el mar azul profundo, pero si tuviera que elegir entre las dos, preferiría la opción 1.
En primer lugar, la opción 2 lanzará una excepción de tiempo de ejecución porque Entity Framework no admite el proyecto en una entidad ( Select(e => new Kitten(...))
y no permite usar un constructor con parámetros en una proyección Ahora, esta nota parece un poco pedante en este contexto, pero al proyectar en la entidad y devolver un Kitten
(o una enumeración de Kitten
), está ocultando el problema real con ese enfoque.
Obviamente, su método devuelve dos propiedades de la entidad que desea usar en su vista: el name
del gatito y imageUrl
. Debido a que estas son solo una selección de todas las propiedades de Kitten
devuelven una entidad de Kitten
(medio lleno) no sería apropiado. Entonces, ¿qué tipo de retorno realmente de este método?
- Podría devolver el
object
(oIEnumerable<object>
) (así es como entiendo su comentario sobre el " método de objeto "), lo cual está bien si pasa el resultado aJson(...)
para procesarlo en Javascript más tarde. Pero perdería toda la información del tipo de tiempo de compilación y dudo que un tipo de resultado deobject
sea útil para cualquier otra cosa. - Podría devolver algún tipo con nombre que solo contenga las dos propiedades, tal vez llamado "KittensListDto".
Ahora, esto es solo un método para una vista: la vista para enumerar gatitos. Luego tiene una vista de detalles para mostrar un solo gatito, luego una vista de edición y luego una vista de confirmación de eliminación, tal vez. Cuatro vistas para una entidad Kitten
existente, cada una de las cuales necesita posiblemente diferentes propiedades y cada una de las cuales necesitaría un método y una proyección independientes y un tipo de DTO diferente. Lo mismo para la entidad Dog
y para 100 entidades más en el proyecto, y quizás obtenga 400 métodos y 400 tipos de devolución.
Y lo más probable es que ni una sola sea reutilizada en ningún otro lugar que no sea este punto de vista específico. ¿Por qué querría Take
10 gatitos con solo name
e imageUrl
cualquier lugar por segunda vez? ¿Tienes una segunda vista de lista de gatitos? Si es así, tendrá una razón y las consultas solo serán idénticas por accidente y ahora, y si una cambia la otra no necesariamente, de lo contrario, la vista de lista no se "reutilizará" correctamente y no debería existir dos veces. ¿O es la misma lista utilizada por una exportación de Excel tal vez? Pero tal vez los usuarios de Excel quieran tener 1000 gatitos mañana, mientras que la vista debería mostrar solo 10. O la vista debería mostrar la Age
del gatito mañana, pero los usuarios de Excel no quieren tener eso porque sus macros de Excel no se ejecutarán correctamente más con ese cambio. Solo porque dos partes de código son idénticas, no tienen que ser factorizadas en un componente reutilizable común si están en un contexto diferente o tienen una semántica diferente. Será mejor que lo deje en GetLatestKittensForListView
y GetLatestKittensForExcelExport
. O mejor no tenga tales métodos en su capa de servicio en absoluto.
A la luz de estas consideraciones, una excursión a una pizzería como una analogía de por qué el primer enfoque es superior :)
"Bienvenido a BigPizza, la tienda de pizzas personalizada, ¿puedo tomar su pedido?" "Bueno, me gustaría tener una pizza con aceitunas, pero salsa de tomate encima y queso en el fondo y hornearla durante 90 minutos hasta que esté negra y dura como una roca plana de granito". "Está bien, señor, las pizzas personalizadas son nuestra profesión, lo lograremos".
El cajero va a la cocina. "Hay un psicópata en el mostrador, él quiere tener una pizza con ... es una piedra de granito con ... espera ... primero necesitamos un nombre", le dice al cocinero.
"¡No!", Grita el cocinero, "¡no de nuevo! Sabes que ya lo intentamos". Él toma una pila de papel con 400 páginas, "aquí tenemos una roca de granito de 2005, pero ... no tenía aceitunas, sino paprica ... o aquí está el mejor tomate ... pero el cliente lo quería Al horno solo medio minuto ". "Tal vez deberíamos llamarlo TopTomatoGraniteRockSpecial ?" "Pero no tiene en cuenta el queso en la parte inferior ..." El cajero: "Eso es lo que se supone que el Special expresa". "Pero tener la roca de la pizza formada como una pirámide también sería especial", responde el cocinero. "Hmmm ... es difícil ...", dice el cajero desesperado.
"¿MI PIZZA YA ESTÁ EN EL HORNO?", De repente grita a través de la puerta de la cocina. "Paremos esta discusión, solo dígame cómo hacer esta pizza, no vamos a tener una pizza así por segunda vez", decide el cocinero. "Está bien, es una pizza con aceitunas, pero salsa de tomate encima y queso en la parte inferior, y hornearla durante 90 minutos hasta que esté negra y dura como una roca plana de granito".
Si la opción 1 viola un principio de separación de inquietudes al usar un contexto de base de datos en la capa de visualización, la opción 2 viola el mismo principio al tener una lógica de consulta centrada en la presentación en la capa de servicio o negocio. Desde un punto de vista técnico, no lo hace, pero terminará con una capa de servicio que no sea "reutilizable" fuera de la capa de presentación. Y tiene costos de desarrollo y mantenimiento mucho más altos porque, por cada pieza de datos requerida en una acción de controlador, tiene que crear servicios, métodos y tipos de devolución.
Ahora, en realidad puede haber consultas o partes de consulta que se reutilizan a menudo y es por eso que creo que la opción 1 es casi tan extrema como la opción 2, por ejemplo, una cláusula Where
por clave (probablemente se usará en detalles, editar y eliminar confirmar) vista), filtrando entidades "eliminadas por software", filtrando por un arrendatario en una arquitectura multi-arrendatario o deshabilitando el seguimiento de cambios, etc. Para tal lógica de consulta realmente repetitiva, podría imaginar que extraer esto en una capa de servicio o repositorio (pero tal vez solo métodos de extensiones reutilizables) podría tener sentido, como
public IQueryable<Kitten> GetKittens()
{
return context.Kittens.AsNoTracking().Where(k => !k.IsDeleted);
}
Cualquier otra cosa que aparezca a continuación, como propiedades de proyección similares, es específica de la vista y no me gustaría tenerla en esta capa. Para hacer posible este enfoque, IQueryable<T>
debe estar expuesto desde el servicio / repositorio. No significa que la select
deba estar directamente en la acción del controlador. Las proyecciones especialmente complejas y complejas (que pueden unirse a otras entidades por las propiedades de navegación, realizar agrupaciones, etc.) podrían trasladarse a métodos de extensión de IQueryable<T>
que se recopilan en otros archivos, directorios o incluso en otro proyecto, pero sigue siendo un proyecto que es un apéndice de la capa de presentación y mucho más cerca de ella que de la capa de servicio. Una acción podría verse así:
public ActionResult Kittens()
{
var result = kittenService.GetKittens()
.Where(kitten => kitten.fluffiness > 10)
.OrderBy(kitten => kitten.name)
.Select(kitten => new {
Name=kitten.name,
Url=kitten.imageUrl
})
.Take(10);
return Json(result,JsonRequestBehavior.AllowGet);
}
O así:
public ActionResult Kittens()
{
var result = kittenService.GetKittens()
.ToKittenListViewModel(10, 10);
return Json(result,JsonRequestBehavior.AllowGet);
}
Con ToKittenListViewModel()
siendo:
public static IEnumerable<object> ToKittenListViewModel(
this IQueryable<Kitten> kittens, int minFluffiness, int pageItems)
{
return kittens
.Where(kitten => kitten.fluffiness > minFluffiness)
.OrderBy(kitten => kitten.name)
.Select(kitten => new {
Name = kitten.name,
Url = kitten.imageUrl
})
.Take(pageItems)
.AsEnumerable()
.Cast<object>();
}
Eso es solo una idea básica y un bosquejo de que otra solución podría estar en el medio entre la opción 1 y la 2.
Bueno, todo depende de la arquitectura y los requisitos generales, y todo lo que escribí anteriormente podría ser inútil e incorrecto. ¿Tiene que considerar que el ORM o la tecnología de acceso a datos podrían cambiar en el futuro? ¿Podría haber un límite físico entre el controlador y la base de datos? ¿Está el controlador desconectado del contexto y es necesario recuperar los datos a través de un servicio web, por ejemplo, en el futuro? Esto requeriría un enfoque muy diferente que se inclinaría más hacia la opción 2.
Tal arquitectura es tan diferente que, en mi opinión, simplemente no puede decir "tal vez" o "no ahora, pero posiblemente podría ser un requisito en el futuro, o posiblemente no lo sea". Esto es algo que las partes interesadas del proyecto tienen que definir antes de que pueda proceder con las decisiones arquitectónicas, ya que aumentará considerablemente los costos de desarrollo y desperdiciaremos dinero en desarrollo y mantenimiento si el "quizás" resulta que nunca se hará realidad.
Estaba hablando solo de consultas o solicitudes GET en una aplicación web que rara vez tienen algo que yo llamaría "lógica de negocios". Las solicitudes POST y la modificación de datos son una historia completamente diferente. Si está prohibido que un pedido se pueda cambiar después de facturarlo, por ejemplo, esta es una "regla de negocios" general que normalmente se aplica sin importar qué vista o servicio web o proceso en segundo plano o lo que sea que intente cambiar un pedido. Definitivamente pondría tal verificación del estado del pedido en un servicio comercial o cualquier componente común y nunca en un controlador.
Puede haber un argumento en contra del uso de IQueryable<T>
en una acción del controlador porque está acoplado a LINQ-to-Entities y dificultará las pruebas unitarias. Pero, ¿qué es una prueba de unidad que se va a probar en una acción de controlador que no contiene ninguna lógica de negocios, que pasa parámetros que generalmente vienen de una vista a través del enlace o enrutamiento del modelo (no cubierto por la prueba de unidad) que utiliza un simulacro? repositorio / servicio que devuelve IEnumerable<T>
: la consulta y el acceso a la base de datos no se comprueban, y eso devuelve una View
: ¿no se prueba la representación correcta de la vista?
No estoy seguro de cómo ASP.NET o C # hace las cosas. Pero sí sé MVC.
En MVC, usted separa su aplicación en dos capas principales: la capa de presentación (que contiene el controlador y la vista) y la capa del modelo (que contiene ... el modelo).
El punto es separar las 3 responsabilidades principales en la aplicación:
- La lógica de la aplicación , la solicitud de manejo, la entrada del usuario, etc. Ese es el controlador.
- La lógica de presentación , manejo de plantillas, visualización, formatos. Esa es la vista.
- La lógica de negocios o "lógica pesada", manejando básicamente todo lo demás. Esa es su aplicación real básicamente, donde se hace todo lo que se supone que debe hacer su aplicación . Esta parte maneja objetos de dominio que representan las estructuras de información de la aplicación, maneja el mapeo de esos objetos en el almacenamiento permanente (ya sea sesión, base de datos o archivos).
Como puede ver, el manejo de la base de datos se encuentra en el Modelo y tiene varias ventajas:
- El controlador está menos ligado al modelo. Debido a que "el trabajo" se realiza en el Modelo, si desea cambiar su controlador, podrá hacerlo más fácilmente si el manejo de su base de datos está en el Modelo.
- Ganas más flexibilidad. En el caso en el que desee cambiar su esquema de mapeo (quiero cambiar a Postgres desde MySQL), solo necesito cambiarlo una vez (en la definición del Mapeador base).
Para obtener más información, consulte la excelente respuesta aquí: ¿Cómo debe estructurarse un modelo en MVC?
Prefiero el segundo enfoque. Al menos se separa entre controlador y lógica empresarial. Todavía es un poco difícil realizar la prueba unitaria (puede que no sea bueno para burlarme).
Personalmente prefiero el siguiente enfoque. La razón principal es que es fácil realizar pruebas unitarias para cada capa: presentación, lógica empresarial, acceso a datos. Además, puedes ver ese enfoque en muchos proyectos de código abierto.
namespace MyProject.Web.Controllers
{
public class MyController : Controller
{
private readonly IKittenService _kittenService ;
public MyController(IKittenService kittenService)
{
_kittenService = kittenService;
}
public ActionResult Kittens()
{
// var result = _kittenService.GetLatestKittens(10);
// Return something.
}
}
}
namespace MyProject.Domain.Kittens
{
public class Kitten
{
public string Name {get; set; }
public string Url {get; set; }
}
}
namespace MyProject.Services.KittenService
{
public interface IKittenService
{
IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10);
}
}
namespace MyProject.Services.KittenService
{
public class KittenService : IKittenService
{
public IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10)
{
using(var db = new KittenEntities())
{
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
}
}
}
Si tuviera (nota: realmente tenía) que elegir entre las 2 opciones dadas, diría 1 por simplicidad, pero no recomiendo usarlo porque es difícil de mantener y causa una gran cantidad de códigos duplicados. Un controlador debe contener la menor lógica de negocios posible. Solo debe delegar el acceso a los datos, asignarlo a un ViewModel y pasarlo a la vista.
Si desea abstraer el acceso a los datos de su controlador (lo cual es bueno), es posible que desee crear una capa de servicio que contenga un método como GetLatestKittens(int fluffinessIndex)
.
Tampoco recomiendo colocar la lógica de acceso a los datos en su POCO, esto no le permite cambiar a otro ORM (NHibernate por ejemplo) y reutilizar los mismos POCO.
Principio de Responsabilidad Única . Cada una de sus clases debe tener una y solo una razón para cambiar. @Zirak da un buen ejemplo de cómo cada persona tiene una única responsabilidad en la cadena de eventos.
Veamos el caso de prueba hipotético que ha proporcionado.
public ActionResult Kittens() // some parameters might be here
{
using(var db = new KittenEntities()){ // db can also be injected,
var result = db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
return Json(result,JsonRequestBehavior.AllowGet);
}
}
Con una capa de servicio intermedia, podría verse algo como esto.
public ActionResult Kittens() // some parameters might be here
{
using(var service = new KittenService())
{
var result = service.GetFluffyKittens();
return Json(result,JsonRequestBehavior.AllowGet);
}
}
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
using(var db = new KittenEntities()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
}
}
Con unas cuantas clases de controladores imaginarios más, puede ver cómo esto sería mucho más fácil de reutilizar. ¡Eso es genial! Tenemos reutilización de código, pero hay aún más beneficio. Digamos, por ejemplo, que nuestro sitio web de Kitten está despegando como loco, todos quieren ver gatitos suaves, por lo que necesitamos particionar nuestra base de datos (fragmento). El constructor de todas nuestras llamadas db debe ser inyectado con la conexión a la base de datos adecuada. Con nuestro código de EF basado en controlador, tendríamos que cambiar los controladores debido a un problema de BASE DE DATOS.
Claramente, eso significa que nuestros controladores ahora dependen de las preocupaciones de la base de datos. Ahora tienen demasiados motivos para cambiar, lo que puede dar lugar a errores accidentales en el código y la necesidad de volver a probar el código que no está relacionado con ese cambio.
Con un servicio, podríamos hacer lo siguiente, mientras que los controladores están protegidos de ese cambio.
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
}
protected KittenEntities GetDbContextForFuffyKittens(){
// ... code to determine the least used shard and get connection string ...
var connectionString = GetShardThatIsntBusy();
return new KittensEntities(connectionString);
}
}
La clave aquí es aislar los cambios para que no lleguen a otras partes de su código. Debe probar todo lo que se ve afectado por un cambio en el código, por lo que desea aislar los cambios entre sí. Esto tiene el efecto secundario de mantener tu código en SECO, por lo que terminas con clases y servicios más flexibles y reutilizables.
Separar las clases también le permite centralizar comportamientos que antes hubieran sido difíciles o repetitivos. Piense en los errores de registro en el acceso a sus datos. En el primer método, necesitarías un registro en todas partes. Con una capa intermedia puede insertar fácilmente alguna lógica de registro.
public class KittenService : IDisposable
{
public IEnumerable<Kitten> GetFluffyKittens()
{
Func<IEnumerable<Kitten>> func = () => {
using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
return db.Kittens // this explicit query is here
.Where(kitten=>kitten.fluffiness > 10)
.Select(kitten=>new {
Name=kitten.name,
Url=kitten.imageUrl
}).Take(10);
}
};
return this.Execute(func);
}
protected KittenEntities GetDbContextForFuffyKittens(){
// ... code to determine the least used shard and get connection string ...
var connectionString = GetShardThatIsntBusy();
return new KittensEntities(connectionString);
}
protected T Execute(Func<T> func){
try
{
return func();
}
catch(Exception ex){
Logging.Log(ex);
throw ex;
}
}
}