Introducción
El diseño de una estructura en capas mucha veces requiere que se trabajen con objetos complejos, por lo general una entidad no tienes solo propiedades simples, algunas puedes representar la relación con otras entidades, es mas puede que la entidad en si sea solo una parte de una mayor
En esta oportunidad nos centraremos justamente en dos aspectos:
- representar una herencia, aquí abordaremos no solo como recuperar una entidad definida con un padre, sino también como persistirla
- cargar entidades relacionadas,
Para esto contaremos con la entidad Instructor en el modelo de administración de la base de datos de una escuela.
El modelo de datos que usaremos define la tabla Persona, pero en la misma tabla se pueden abstraer otras dos entidades Instructor y Alumno.
La implementación de la herencia en este caso lleva el nombre de “tabla por subclase”, en donde la relación uno a uno con la tabla OfficeAssigment determina si es un instructor o un Alumno, en este caso no se usa ningún campo discriminador para el tipo, la relación actúa como medio para determinarlo. Si hay una relación con la tabla OfficeAssigment será un instructor, sino lo hay será un alumno.
Herencia de entidades (Recuperar entidad)
Las entidades intervinientes en este modelo se representan en la siguiente imagen:
A simple vista se puede observar que la entidad Instructor hereda de persona, la pregunta que trataremos de responder es como definir un modelo de persistencia para esta entidad.
Empezaremos analizando la clase InstructorRepository la cual cuenta con el método:
public static InstructorEntity GetByKey(int id) { InstructorEntity item = null; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string query = @"SELECT P.PersonID, P.LastName, P.FirstName, P.HireDate, P.EnrollmentDate, OA.Location FROM Person P INNER JOIN OfficeAssignment OA ON P.PersonID = OA.InstructorID WHERE P.PersonID = @id ORDER BY P.PersonID"; SqlCommand cmd = new SqlCommand(query, conn); cmd.Parameters.Add("@id", SqlDbType.Int).Value = id; SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); if (reader.Read()) { item = ConvertInstructor(reader); } } return item; }
private static InstructorEntity ConvertInstructor(IDataReader reader) { InstructorEntity item = new InstructorEntity(); item.PersonID = Convert.ToInt32(reader["PersonID"]); item.LastName = Convert.ToString(reader["LastName"]); item.FirstName = Convert.ToString(reader["FirstName"]); item.HireDate = reader["HireDate"] == DBNull.Value ? (DateTime?)null : Convert.ToDateTime(reader["HireDate"]); item.Location = Convert.ToString(reader["Location"]); return item; }
Recuperar una entidad, o una lista de Instructores no parece diferir mucho a como se haría con una entidad simple, en este caso la query involucra tanto la tabla del Instructores como la de Personas, lo que implica usar el INNER JOIN para unir los registros.
Herencia de entidades (Crear entidad)
Donde si veremos mayor cambios es al momento de actualizar la entidad, pues requiere impactar las actualizaciones en dos tablas diferentes
Empecemos por crear un nuevo instructor, la clase InstructorRepository contiene el método Save()
Definir un instructor implica varios pasos:
- crear el registro en la tabla base, en este caso insertar el registro en Persona
- crear el registro en la tabla OfficeAssignment
- si la entidad tenia cursos asignados se crea la relación con esto
1- Grabar la entidad Persona, esta operación es bien simple, solo implica un INSERT en la tabla y recuperar el id generado.
public static void Save(PersonEntity person) { string sql = @"INSERT INTO Person ( LastName, FirstName) VALUES (@LastName, @FirstName); SELECT SCOPE_IDENTITY"; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@LastName", person.LastName); cmd.Parameters.AddWithValue("@FirstName", person.FirstName); person.PersonID = Convert.ToInt32(cmd.ExecuteScalar()); } }
2- Aquí no solo se actualiza los datos concretos del instructor en la tabla padre, sino que además se inserta en la tabla concreta que define el tipo, en esta operación se hace uso del mismo id que se recupero al crear la entidad padre.
public static void Save(InstructorEntity instructor) { PersonRepository.Save((PersonEntity)instructor); using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); // // Actualiza los campos de la tabla Persona // con el campo que define solo el instructor // string sqlUpdateP = @"UPDATE Person SET HireDate = @HireDate WHERE PersonID = @PersonID"; using (SqlCommand cmd = new SqlCommand(sqlUpdateP, conn)) { cmd.Parameters.AddWithValue("@HireDate", instructor.HireDate.HasValue ? instructor.HireDate.Value : (object)DBNull.Value); cmd.Parameters.AddWithValue("@PersonID", instructor.PersonID); cmd.ExecuteNonQuery(); } // // Inserta el registro que define al instructor concretamente // string spInsertOA = @"INSERT OfficeAssignment (InstructorID, Location) VALUES (@InstructorID, @Location)"; using (SqlCommand cmd = new SqlCommand(spInsertOA, conn)) { cmd.Parameters.AddWithValue("@InstructorID", instructor.PersonID); cmd.Parameters.AddWithValue("@Location", instructor.Location); cmd.ExecuteNonQuery(); } } // // Se procesa los cursos asignados // CourseRepository.RelateWithPerson((PersonEntity)instructor, instructor.Courses); }
3- En caso de existir entidades relacionadas se realiza la operación de merge entre los datos provenientes de la selección del usuario y los datos existentes en la tabla
Para realizar la tarea de forma simple se elimina toda relación y se procede a crearlas nuevamente, pero si se anima se podría haber utilizado la instrucción MERGE de T-SQL.
En la línea:
// // Se procesa los cursos asignados // CourseRepository.RelateWithPerson((PersonEntity)instructor, instructor.Courses);
Se invoca la clase responsable de crear la relación entre las entidad persona y cursos.
/// <summary> /// Dada una persona y una lista de cursos se crea la relacion entre las entidades. /// /// Para implementar un merge simple que permita registrar los cursos agregados o eliminados por el usuario, /// se realiza se elimina toda la relacion y volverla a insertar /// </summary> /// <param name="person"></param> /// <param name="courses"></param> public static void RelateWithPerson(PersonEntity person, List<CourseEntity> courses) { using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); //se elimina la relacion existentes string sqlDelete = @"DELETE CourseInstructor WHERE PersonID = @PersonID"; using (SqlCommand cmd = new SqlCommand(sqlDelete, conn)) { cmd.Parameters.AddWithValue("@PersonID", person.PersonID); cmd.ExecuteNonQuery(); } //se relaciona los cursos asociados a la entidad string sqlCourseInstructor = @"INSERT CourseInstructor (CourseID, PersonID) VALUES (@CourseID, @PersonID)"; using (SqlCommand cmd = new SqlCommand(sqlCourseInstructor, conn)) { foreach (CourseEntity course in courses) { cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@CourseID", course.CourseID); cmd.Parameters.AddWithValue("@PersonID", person.PersonID); cmd.ExecuteNonQuery(); } } } }
Herencia de entidades (Actualizar entidad)
La actualización de une entidad que implementar una herencia es muy similar a la creación.
- actualizar el registro en la tabla base
- actualizar el registro en la tabla OfficeAssignment
- si la entidad tenia cursos asignados se crea la relación con esto
1- Se invoca al metodo Update() de PersonRepository
public static void Update(PersonEntity person) { string sql = @"UPDATE Person SET LastName = @LastName, FirstName = @FirstName WHERE PersonID = @PersonID"; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@LastName", person.LastName); cmd.Parameters.AddWithValue("@FirstName", person.FirstName); cmd.Parameters.AddWithValue("@PersonID", person.PersonID); cmd.ExecuteNonQuery(); } }
2 – Se actualiza la tabla que define al Instructor, este se define en el método Update() de la clase InstructorRepository.
public static void Update(InstructorEntity instructor) { // //Se actualiza el registro de la Persona // PersonRepository.Update((PersonEntity)instructor); using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); // // Actualiza los campos de la tabla Persona // con el campo que define solo el instructor // string sqlUpdateP = @"UPDATE Person SET HireDate = @HireDate WHERE PersonID = @PersonID"; using (SqlCommand cmd = new SqlCommand(sqlUpdateP, conn)) { cmd.Parameters.AddWithValue("@HireDate", instructor.HireDate.HasValue ? instructor.HireDate.Value : (object)DBNull.Value); cmd.Parameters.AddWithValue("@PersonID", instructor.PersonID); cmd.ExecuteNonQuery(); } // // Actualiza el registro que define al instructor concretamente // string sqlUpdateOA = @"UPDATE OfficeAssignment SET Location = @Location WHERE InstructorID = @InstructorID"; using (SqlCommand cmd = new SqlCommand(sqlUpdateOA, conn)) { cmd.Parameters.AddWithValue("@InstructorID", instructor.PersonID); cmd.Parameters.AddWithValue("@Location", instructor.Location); cmd.ExecuteNonQuery(); } } // // Se procesa los cursos asignados // CourseRepository.RelateWithPerson((PersonEntity)instructor, instructor.Courses); }
3 – Al igual que al crear la entidad se actualizan las relaciones con las demás entidades, en este caso se aplica el mismo código para reflejar la relación con los cursos.
Código
[c#]
|
Hola leandro! tengo una duda..en los foros ayer me ayudaste a remodelar mi capa de datos con entidades, y en el ejemplo te habia mostrado esta entidad:
ResponderEliminarpublic class LocalidadBaseEntity
{
public int id_localidad { get; set; }
public string nombre_localidad { get; set; }
public int cod_postal { get; set; }
public int id_provincia { get; set; }
public string usuario { get; set; }
}
Si por ejemplo, en una consulta necesitaria traer el campo "nombre_provincia" de otra tabla, deberia crear otra entidad para ello no, de esta manera?
public class LocalidadCompletaEntity
{
public int id_localidad { get; set; }
public string nombre_localidad { get; set; }
public int cod_postal { get; set; }
public int id_provincia { get; set; }
public string nombre_provincia { get; set; }
}
y por ejemplo, para las altas, modificaciones, pasaria la primera; y en el caso de querer una query mas completa, pasaria la segunda entidad...saludos!
hola matias
ResponderEliminarpero en este caso que planteas podrias aplicar herencia
public class LocalidadCompletaEntity : LocalidadBaseEntity
{
public string nombre_provincia { get; set; }
}
porque la entidad completa es igual a la Base solo que agregas una propiedad adicional
saludos
gracias leandro! recien estoy haciendo mis primeras pruebas con entidades, y no se me habia ocurrido!
ResponderEliminarBuen dia leandro, quiero saber si usando un gridview puedo editar/actualizar las celdas sin el uso de los botones editar/actualizar?
ResponderEliminarhola Jose
ResponderEliminarhasta donde se no se puede, debes contar con las opciones ya sean link o botones que permitan pasar a edicion una row, ademas de aceptar o cancelar el cambio
saludos
Perdon Leandro yo se que talvez la pregunta que deseo realizarte no es con respecto al tema fijate que tengo un lector QUE ES UN LECTOR DE HUELLAS MC7XFPR-01R motorola y necesito programarlo ya tengo el sdk pero todavia no encuentro como hacer que compare la huellas digitales y guardarlas de antemano muchas gracias espero tu ayuda
ResponderEliminarhola Der8578
ResponderEliminarpero si tienes un sdk que corresponde al modelo del lector este tiene que venir con ejemplos de codigo mostrando como usar el api para realizar comparaciones
tambien deberia venir con algun archivo (quizas un chm) de ayuda aunque sea con la info de los metodo de las librerias que debes usar
saludos
hola elvis
ResponderEliminarrespondi en el mail
saludos
Saludos Leandro,
ResponderEliminarNuevamente te escribo para saber si puedes darme una idea de como puedo grabar y exportar los datos contenidos en una Listview de 7 columnas en un archivo XML. He intentado usar la Serializacion pero tengo errores por todos lados.
En esta parte de mi aplicacion, debo hacer click sobre un boton que permita guardar el contenido de mi Listview en un archivo XML.
Agradezco de antemano tu respuesta.
hola Federico
ResponderEliminarpero estas volcando los datos del listview a una clase para poder serializarla?
o sea el mismo control no se serializa, debes levarlo a una clase
Cómo serializar un objeto
saludo
Hola Leandro, tengo una duda como la que te plantea matias p.
ResponderEliminarSupongamoa que tengo una entidad Producto que contiene entre varios atributos las siguientes FK (ID_categoria), (ID_Provedor) estan definidos como int, pero en ciertos metodos van a ser cargados con string a travez de una consulta con INNER JOIN.
¿Debo crear otra entidad para manejar esos casos o debo agregar una propiedad (string) adicional, a mi entidad ya existente? O En esos casos cuando tengo una entidad con una FK debo crear un List?
Luego tengo otro duda al datasource de un datagridview le cargo la entidad producto a modo de ejemplo de sus 20 campos solo quiero mostrar 3.
El filtrado para q me muestre solo esos 3 campos lo tengo q hacer en el dgv no? no lo estoy podiendo resolver y me preguntaba si tal vez lo este haciendo mal, tal vez no tenga que traer la entidad completa.
Gracias por todo!
hola Luis
ResponderEliminarPero porque tendrias que cargar como string un id de la entidad, eso que los metodos carguen un string no es correcto
en el inner join usas los id como int para relacionar las tablas
Para mostrar solo 3 campos en el grid deberias definir estas columnas en tiempo de diseño, a la columna le asignas el DatapropertyName indicando el nombre de la propiedad que mapea con esa columna
de esta forma por mas que tengas 20 propiedades en el objeto que aisgnas al datasource el grid solo le interesaran los 3 que definas
en la coleccion columns del grid es donde defines esto
saludos
Muchas Gracias por tu respuesta Leandro, el problema que tengo es que no estoy entendiendo bien la logica de las entidades.
ResponderEliminarSigo tus ejemplos pero con algunas cosas me pierdo.
Es que si tengo estos campos en mi entidad producto (ProductoEntity):
int Id_Produco (PK)
String NombreProducto
Int Id_Categoria (FK)
luego creo una instancia:
ProductoEntity producto = new ProductoEntity();
Luego ejecuto una consulta para llenar esa entidad, pero quiero obtener el nombre de la categoria
SELECT p.id_producto, pNombre, cNombre
FROM Producto AS p
INNER JOIN Categoria AS c
ON p.id_producto = c.Idproducto
Yo no tengo un campo string en el producto para mostrar el nombre de la categoria, eso es lo que no me cierra bien? Como se maneja eso? debo usar dos entidades?
Te agradesco por tu respuesta y se que estoy preguntando algo tonto, pero no lo estoy entendiendo bién
hola Luis
ResponderEliminaraqui hay que diferenciar dos cosas, una es la entidad de negocio y otra es como muestras la informacion
en tu entidad de negocio puede definir propiedades de navegacion que una una entidad con otra
pero cuando llevas esto a al UI esta claro que debes aplanar los datos
para ayudarte en esta tarea podrias crear una clase deferente en el proyecto de UI que sea
public class ProductoModel
{
public int Id_Produco {get; set;}
public String NombreProducto {get; set;}
public int Id_Categoria {get; set;}
public string CategoriaDesc {get; set;}
}
esta clase la usaria solo en UI ayudandote con automapper
con este podrias autoamtizar la conversion de una clase a otra facilmente
saludos
hola...podrías decirme por que sale este error
ResponderEliminarrimera excepción del tipo 'System.ArgumentOutOfRangeException' en mscorlib.dll
Información adicional: El índice estaba fuera del intervalo. Debe ser un valor no negativo e inferior al tamaño de la colección.
Si hay un controlador para esta excepción, el programa puede continuar de forma segura.
hola
EliminarEntiendo que deebs de estar usando un array o lista y el indice que quieres acceder pasa las dimensiones que define
Sin analizar el codigo no podria decir mucho mas, pero podrias poner un breakpoint en el codigo para inspeccionar el valor que toma las variables
saludos
Leandro, buen día.
ResponderEliminarYo tengo una duda respecto a una relación de entidades para un datagridview.
Quisiera que me envíes un mensaje a pepejose_14@hotmail.com para poder charlar de la mejor manera y poder entender.
Muchos de tus tutoriales me ayudan y sigo avanzando, pero está vez necesito personalmente la ayuda.
Espero tu pronta respuesta.
Saludos.
Atte. José
Hola Leandro:
ResponderEliminarTe agradezco por este interesante y útil tema que has tratado con respecto a la herencia de entidades.
Me queda la duda de si la columna HireDate en la tabla Person debería estar en la tabla OfficeAssignment que trata con datos de Instructor, así como la columna EnrollmentDate que se refiere a datos de Student también debería ir en otra tabla denominada Student.
Mucho agradecería tu explicación de tener HireDate y EnrollmentDate en Person.
Gracias de antemano Leandro.
Xabier
PS. Los links para poder descargar los ejemplos no parecen funcionar correctamente. ¿Habrá algún problema?
hola
EliminarEn realidad la ubicacion de las columnas va a depender de como modeles la herencia de las entidades, hay varias formas de lograrlo, podrias definir todo en una tabla y usar una columnas discriminador para conocer si registras un instructor o un estudiante, en este caso no me enfoque en la herencia concretamente sino mas bien en las relaciones. En este caso solo registro instructores en la tabla persona.
Aqui
[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)
explico algo de esto usando entity framework, como veras las entidades mapean a una unica tabla pero se define el campo Type como discriminador de tipo.
Puedes hacerlo de esta forma o en tablas separadas
[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)
Valide los links y estan correctos, estan alojados en OneDrive, no deberia causar problemas el acceso pero por las dudas usa un browser actualizado para acceder
saludos
¡Qué tal Leandro!
EliminarMuchas gracias por tu respuesta, respecto a las descargas he intentado con varios navegadores como Mozilla, Chrome, Opera e incluso Edge (ya que cuento con un equipo que tiene Win 10 Pro), con las versiones más recientes; pero ha sido en vano.
En lugar del icono de la descarga aparece el texto: "No se puede cargar este elemento ahora." y si doy click me redirecciona a: "https://onedrive.live.com/about/es-es/" sin darme opción de descargar nada.
Recuerdo que ya hace algún buen tiempo sí podía descargar los archivos, e intenté con otras versiones de Windows como XP y Win-7 y distintos navegadores; pero también todo fue en vano.
De todas maneras lo importante es el contenido de tus artículos y con eso me quedo.
Saludos
Xabier
¡Hola Leandro!
EliminarHoy acabo de intentar descargar el código y ya lo he logrado con el navegador que siempre uso que es Mozilla.
Por otro lado, si intento descargar el código del artículo "[jQuery] RadioButton y CheckBox" no aparece el icono amarillo de descarga, y si de todas maneras hago click me redirecciona a otro página donde me aparece el error:
"Firefox can’t find the server at www.ltuttini.com.ar."
es decir, no me redirecciona a onedrive para la descarga.
Tal vez haya un problema en el servidor donde se hospedan los archivos.
Un saludo y muchas gracias por compartir tus conocimientos.
Xabier