studio programacion móviles libro desarrollo datos curso capa aplicaciones acceso .net .net-2.0 data-access-layer

.net - móviles - manual de programacion android pdf



Cómo mejorar el método de selección de capa de acceso a datos Patrón (7)

Últimamente me encuentro escribiendo métodos de selección de capas de acceso a datos donde el código toma esta forma general:

public static DataTable GetSomeData( ... arguments) { string sql = " ... sql string here: often it''s just a stored procedure name ... "; DataTable result = new DataTable(); // GetOpenConnection() is a private method in the class: // it manages the connection string and returns an open and ready connection using (SqlConnection cn = GetOpenConnection()) using (SqlCommand cmd = new SqlCommand(sql, cn)) { // could be any number of parameters, each with a different type cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function using (SqlDataReader rdr = cmd.ExecuteReader()) { result.Load(rdr); } } return result; }

O así:

public static DataRow GetSomeSingleRecord( ... arguments) { string sql = " ... sql string here: often it''s just a stored procedure name ... "; DataTable dt = new DataTable(); // GetOpenConnection() is a private method in the class: // it manages the connection string and returns an open and ready connection using (SqlConnection cn = GetOpenConnection()) using (SqlCommand cmd = new SqlCommand(sql, cn)) { // could be any number of parameters, each with a different type cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function using (SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SingleRow)) { dt.Load(rdr); } } if (dt.Rows.Count > 0) return dt.Rows[0]; return null; }

Estos métodos serían llamados por código de capa de negocio que luego convierte DataTable base o DataRecord en objetos comerciales fuertemente tipados que el nivel de presentación puede usar.

Como uso un código similar repetidamente, quiero asegurarme de que este código sea lo mejor posible. Entonces, ¿cómo se puede mejorar? Y, ¿vale la pena tratar de mover el código común de esto a su propio método? Si es así, ¿cómo se vería ese método (específicamente con respecto a pasar una colección SqlParameter)?


Hay tantas maneras de implementar el DBAL, en mi opinión, estás en el camino correcto. Somethings a considerar en su implementación:

  • Está utilizando un método similar a la fábrica para crear su SqlConnection, es un punto menor pero puede hacer lo mismo con su SqlCommand.
  • La longitud del parámetro es opcional, por lo que puede dejarlo fuera de la llamada Parameter.Add.
  • Crea métodos para agregar parámetros también, muestra el código a continuación.

Agregue parámetros usando DbUtil.AddParameter(cmd, "@Id", SqlDbType.UniqueIdentifier, Id);

internal class DbUtil { internal static SqlParameter CreateSqlParameter( string parameterName, SqlDbType dbType, ParameterDirection direction, object value ) { SqlParameter parameter = new SqlParameter(parameterName, dbType); if (value == null) { value = DBNull.Value; } parameter.Value = value; parameter.Direction = direction; return parameter; } internal static SqlParameter AddParameter( SqlCommand sqlCommand, string parameterName, SqlDbType dbType ) { return AddParameter(sqlCommand, parameterName, dbType, null); } internal static SqlParameter AddParameter( SqlCommand sqlCommand, string parameterName, SqlDbType dbType, object value ) { return AddParameter(sqlCommand, parameterName, dbType, ParameterDirection.Input, value); } internal static SqlParameter AddParameter( SqlCommand sqlCommand, string parameterName, SqlDbType dbType, ParameterDirection direction, object value ) { SqlParameter parameter = CreateSqlParameter(parameterName, dbType, direction, value); sqlCommand.Parameters.Add(parameter); return parameter; } }


Lo único que hago diferente es cambiar de mis propios métodos internos de ayuda de la base de datos al bloque de aplicación de acceso a datos actual http://msdn.microsoft.com/en-us/library/cc309504.aspx

Lo hace un poco más estandarizado / uniforme para otros desarrolladores que conocen la biblioteca empresarial para aumentar el código.


Un patrón que he disfrutado se ve así en lo que respecta al código del cliente:

DataTable data = null; using (StoredProcedure proc = new StoredProcedure("MyProcName","[Connection]")) { proc.AddParameter("@LoginName", loginName); data = proc.ExecuteDataTable(); }

Normalmente hago que la conexión sea opcional, y codificaré de una manera para extraerla de la sección de configuración de ConnectionStrings o tratarla como la cadena de conexión real. Esto me permite reutilizar el dal en un escenario único y es en parte un habito de los días COM + cuando almacené la cadena de conexión usando la propiedad de construcción del objeto.

Me gusta porque es fácil de leer y oculta todo el código ADO de mí.


En primer lugar, creo que ya consideró usar un ORM y no hacer rodar el suyo. No entraré en esto.

Mis pensamientos sobre rodar tu propio código de acceso a datos:

  • Con el tiempo, me resultó más fácil no tener objetos DAL / BL por separado, sino unirlos en un solo objeto (algún tiempo después de llegar a esta conclusión descubrí que es un patrón bastante conocido, a saber, ActiveRecord). Puede que se vea bien y desacoplado para tener ensamblajes DAL separados, pero la sobrecarga en costos de mantenimiento se acumulará. Cada vez que agregue una nueva característica, tendrá que crear más código / modificar más clases. En mi experiencia, el equipo que mantiene la aplicación a menudo es mucho menos que el equipo original de desarrolladores que la desarrolló, y odiarán el trabajo adicional requerido.
  • Para equipos grandes, podría tener sentido separar el DAL (y dejar que un grupo trabaje en él mientras que los demás. Pero esto constituye un buen incentivo para la inflamación del código.
  • Bajando a su muestra específica: ¿cómo se usa la DataTable resultante? Iterar las filas, crear objetos tipeados y obtener los datos de la fila? Si la respuesta es sí, piense en la DataTable adicional que creó solo para mover datos entre el DAL y el BL. ¿Por qué no tomarlo directamente del DataReader?
  • También sobre la muestra: si devuelve un DataTable sin tipo, entonces supongo que debe usar los nombres de columna (del conjunto de resultados que devuelve la llamada SP) hacia arriba en el código de llamada. Esto significa que si tengo que cambiar algo en la base de datos, podría afectar a ambas capas.

Mi sugerencia (probé ambos métodos, la sugerencia es el último enfoque de trabajo que se me ocurrió, evolucionó con el tiempo).

  • Haga una clase base para sus objetos comerciales tipados.
  • Mantener el estado del objeto en la clase base (nuevo, modificado, etc.)
  • Coloque los principales métodos de acceso a datos en esta clase, como métodos estáticos. Con un poco de esfuerzo (sugerencia: métodos genéricos + Activator.CreateInstance) puede crear un objeto comercial por cada fila devuelta en el lector.
  • crea un método abstracto en el objeto comercial para analizar los datos de la fila (directamente desde el DataReader!) y llena el objeto.
  • crear métodos estáticos en los objetos comerciales derivados que preparan los parámetros de proceso almacenados (dependiendo de varios criterios de filtro) y llamar a los métodos genéricos de acceso a datos de la clase base.

El objetivo es terminar con el uso como:

List<MyObject> objects = MyObject.FindMyObject(string someParam);

El beneficio para mí fue que solo tuve que cambiar un archivo para hacer frente a los cambios en los nombres de columna de la base de datos, tipos, etc. (pequeños cambios en general). Con algunas regiones bien pensadas, puede organizar el código para que sean "capas" separadas en el mismo objeto :). El otro beneficio es que la clase base es realmente reutilizable de un proyecto a otro. Y la saturación del código es mínima (bueno, en comparación con los beneficios. También podría completar los conjuntos de datos y vincularlos a los controles de la interfaz de usuario: D

Las limitaciones: terminas con una clase por objeto de dominio (generalmente por tabla de base de datos principal). Y no puede cargar objetos en transacciones existentes (aunque podría pensar en pasar la transacción, si tiene una).

Avíseme si le interesan más detalles: podría ampliar la respuesta un poco.


Similar a lo que publiqué aquí

public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, Func<IDataRecord, S> selector) { using (var conn = new T()) //your connection object { using (var cmd = conn.CreateCommand()) { if (parameterizer != null) parameterizer(cmd); cmd.CommandText = query; cmd.Connection.ConnectionString = _connectionString; cmd.Connection.Open(); using (var r = cmd.ExecuteReader()) while (r.Read()) yield return selector(r); } } }

Tengo estos simples métodos de extensión para facilitar la llamada:

public static void Parameterize(this IDbCommand command, string name, object value) { var parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = value; command.Parameters.Add(parameter); } public static T To<T>(this IDataRecord dr, int index, T defaultValue = default(T), Func<object, T> converter = null) { return dr[index].To<T>(defaultValue, converter); } static T To<T>(this object obj, T defaultValue, Func<object, T> converter) { if (obj.IsNull()) return defaultValue; return converter == null ? (T)obj : converter(obj); } public static bool IsNull<T>(this T obj) where T : class { return (object)obj == null || obj == DBNull.Value; }

Entonces ahora puedo llamar:

var query = Get(sql, cmd => { cmd.Parameterize("saved", 1); cmd.Parameterize("name", "abel"); }, r => new User(r.To<int>(0), r.To<string>(1), r.To<DateTime?>(2), r.To<bool>(3))); foreach (var user in query) { }

Esto es completamente genérico, se adapta a cualquier modelo que cumpla con las interfaces de ado.net. El objeto de conexión y el lector se eliminan solo después de que la colección se enumera una vez.


Tuve que agregar el mío:
Devuelve DataReader desde DataLayer en la instrucción Using

El nuevo patrón me permite tener solo un registro en la memoria a la vez, pero aún encierra la conexión en una agradable declaración de ''uso'':

public IEnumerable<T> GetSomeData(string filter, Func<IDataRecord, T> factory) { string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter"; using (SqlConnection cn = new SqlConnection(GetConnectionString())) using (SqlCommand cmd = new SqlCommand(sql, cn)) { cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter; cn.Open(); using (IDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { yield return factory(rdr); } rdr.Close(); } } }


La solución más simple:

var dt=new DataTable(); dt.Load(myDataReader); list<DataRow> dr=dt.AsEnumerable().ToList();