tag:blogger.com,1999:blog-73618928407934991282024-03-13T03:03:32.823-07:00Leandro Tuttini BlogAnonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.comBlogger157125tag:blogger.com,1999:blog-7361892840793499128.post-42691615963381530492014-11-05T04:49:00.000-08:002014-11-06T05:13:03.791-08:00[Entity Framework] Code First - Cargando Entidades Relacionadas<p> </p> <p>Las asociaciones pueden trabajarse por lazy load o definir explícitamente que cargue una propiedad relacionada, esos temas serán tratados en el video, así como se comportan las entidades cuando están fuera del contexto.</p> <p>También veremos como analizar las queries que EF genera, utilizando Sql Server Profiler.</p> <p> </p> <p><iframe style="height: 503px; width: 805px" src="//channel9.msdn.com/Blogs/DevWow/100devdays-Cargando-Entidades-Relacionadas/player?h=503&w=805&format=smooth" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p> <p> </p> <p><strong>Link <hr /></strong></p> <p><a href="http://channel9.msdn.com/Blogs/DevWow/100devdays-Cargando-Entidades-Relacionadas" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-a4xxGss0jzA/VFtz3gsGYYI/AAAAAAAACGg/phNRcBuA-r0/image%25255B4%25255D.png?imgmax=800" width="240" height="154" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com69tag:blogger.com,1999:blog-7361892840793499128.post-65304016552095266792014-11-04T06:43:00.000-08:002014-11-05T06:44:43.608-08:00[Entity Framework] Code First - Manejando Relaciones<p> </p> <p>Cuando se trabaja con entidades es muy raro que se encuentren aisladas por lo general estas se asocian con otras para colaborar.</p> <p>Las asociaciones que trato en el video serán: uno a uno, uno a muchos o muchos a muchos.</p> <p> </p> <p><iframe style="height: 476px; width: 803px" src="//channel9.msdn.com/Blogs/DevWow/100devdays-Manejando-Relaciones/player?h=476&w=803&format=smooth" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p> <p> </p> <p><strong>Link </strong> <hr /></p> <p><a href="http://channel9.msdn.com/Blogs/DevWow/100devdays-Manejando-Relaciones" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-EpfAkZzWYIk/VFo3zAP8Q2I/AAAAAAAACGQ/eGmLVA1dZlY/image%25255B4%25255D.png?imgmax=800" width="240" height="145" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com36tag:blogger.com,1999:blog-7361892840793499128.post-27105447460816177802014-11-03T05:16:00.000-08:002014-11-05T06:08:17.062-08:00[Entity Framework] Code First - Consultando entidades<p> </p> <p>Después de configurar el modelo será necesario aprender a operar con el mismo para obtener la información que nuestra aplicación necesita.</p> <p>Las queries linq que definamos serán transformadas a consultas sql dinámicamente por EF, por eso es importante entender como funcionan, aquí verán como trabajar entidades simple, como agruparlas, herencia, tipos dinámicos, etc.</p> <p> </p> <p><iframe style="height: 468px; width: 802px" src="//channel9.msdn.com/Blogs/DevWow/100devdays-Consultando-entidades/player?h=468&w=802&format=smooth" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p> <p> </p> <p><strong>Link <hr /></strong></p> <p><a href="http://channel9.msdn.com/Blogs/DevWow/100devdays-Consultando-entidades" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-LUNUX1k3Dmg/VFovT88AgHI/AAAAAAAACGA/euaRsPw70cs/image%25255B4%25255D.png?imgmax=800" width="240" height="143" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com37tag:blogger.com,1999:blog-7361892840793499128.post-42603941378654392332014-11-02T11:31:00.000-08:002014-11-05T04:58:27.741-08:00[Entity Framework] Code First - Fluent Api<p> </p> <p>En el video explico como por medio de código se define el mapping de las entidades pudiendo especificar: clave principal, tipos y precisión en los datos, relaciones entre entidades, herencia, etc.</p> <p>Por medio de código podrán redefinir las relaciones cuando estas no siguen las convenciones.</p> <p> </p> <p><iframe style="height: 465px; width: 793px" src="//channel9.msdn.com/Blogs/DevWow/100devday-Code-First-Fluent-Api/player?h=465&w=793&format=smooth" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p> <p> </p> <p><strong>Link </strong> <hr /></p> <p><a href="http://channel9.msdn.com/Blogs/DevWow/100devday-Code-First-Fluent-Api" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-BRg9XGqgg1Y/VFfcnKYW-eI/AAAAAAAACEQ/p5GmFG35_UM/image%25255B4%25255D.png?imgmax=800" width="240" height="143" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com31tag:blogger.com,1999:blog-7361892840793499128.post-81971503306114396412014-11-01T12:06:00.000-07:002014-11-05T04:55:35.578-08:00[Entity Framework] Code First - Convenciones<p> </p> <p>Les comparto este video donde explico las convenciones que Entity Framework utiliza para descubrir los tipos de datos, la clave principal, las relaciones entre entidades, etc.</p> <p>Podemos definir nuestro modelo sin necesidad de escribir explícitamente en código como mapea con la base de datos, solo será necesario seguir ciertas normas preestablecidas en las convenciones. </p> <p> </p> <p><iframe style="height: 434px; width: 794px" src="//channel9.msdn.com/Blogs/DevWow/100devdays-Code-First-Convenciones/player?h=434&w=794&format=smooth" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p> <p> </p> <p> </p> <p><strong>Link </strong> <hr /></p> <p><a href="http://channel9.msdn.com/Blogs/DevWow/100devdays-Code-First-Convenciones" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-28_MxTndnwQ/VFfXv2UCBrI/AAAAAAAACDw/b3yUzELIMKw/image%25255B19%25255D.png?imgmax=800" width="286" height="184" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com28tag:blogger.com,1999:blog-7361892840793499128.post-70731755392264037732014-10-01T06:49:00.000-07:002015-02-22T06:08:28.599-08:00MVP C# 2014<p> </p> <p>Gracias por haber confiando una vez mas, no puedo cree que ya pasaron 5 años.</p> <p> </p> <p><a href="http://lh6.ggpht.com/-089lwHfiTlA/VOnisaaPA1I/AAAAAAAACNw/xn7xI5GsGw4/s1600-h/20141224_122424%25255B7%25255D.jpg" target="_blank"><img title="Tuttini MVP 2014" style="display: inline" alt="Tuttini MVP 2014" src="http://lh5.ggpht.com/-qBJLZOpRS9M/VOniuqehJGI/AAAAAAAACN4/UqtNdVx4vc8/20141224_122424_thumb%25255B4%25255D.jpg?imgmax=800" width="614" height="468" /></a></p> <p> </p> <p><a href="http://lh3.ggpht.com/-4JijOdhyg50/VOniwS2UPzI/AAAAAAAACOA/C0v8oG6aHyw/s1600-h/20141224_122612%25255B8%25255D.jpg" target="_blank"><img title="Tuttini MVP 2014" style="display: inline" alt="Tuttini MVP 2014" src="http://lh3.ggpht.com/-bwR6djy5LWk/VOniyEnVUXI/AAAAAAAACOI/vr-JTdivizk/20141224_122612_thumb%25255B5%25255D.jpg?imgmax=800" width="218" height="283" /></a>  <a href="http://lh3.ggpht.com/-sLDs6bWRPns/VOniz4eepkI/AAAAAAAACOQ/vqn0BdSnKpM/s1600-h/20141224_122544%25255B6%25255D.jpg"><img title="Tuttini MVP 2014" style="display: inline" alt="Tuttini MVP 2014" src="http://lh6.ggpht.com/-9sAyg3e38Mk/VOni13hVp_I/AAAAAAAACOY/dpNI8V99NnY/20141224_122544_thumb%25255B3%25255D.jpg?imgmax=800" width="358" height="273" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com30tag:blogger.com,1999:blog-7361892840793499128.post-2049486781722410702014-05-22T14:38:00.001-07:002014-05-22T14:38:04.310-07:00Comunicar Formularios<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Existen varias formas pasar información entre formularios, especialmente si queremos hacerlo sin generar una fuerte dependencia entre estos.</p> <p>Este mismo tema lo había abordado en artículos anteriores por lo que retomaremos el análisis de las distintas formas de lograr la comunicación sin generar dependencia entre los forms</p> <p>Los artículos anteriores en los cuales he tratado el tema:</p> <p><a href="http://ltuttini.blogspot.com.ar/2009/09/c-comunicar-formularios-de-forma.html">Comunicar formularios de forma desacoplada</a></p> <p><a href="http://ltuttini.blogspot.com.ar/2009/09/c-comunicar-formularios-mdi.html">Comunicar formularios MDI</a></p> <p>Para ejemplificar haremos uso de un form que permite listar y editar productos de la base de datos Northwind.</p> <p><a href="http://lh5.ggpht.com/-gadChoYeVzQ/U35txEfiyLI/AAAAAAAABus/c-8vntH_A6E/s1600-h/image24.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-nYoyIKUsssA/U35tyOJRS6I/AAAAAAAABu0/2P97RdOOxi4/image_thumb16.png?imgmax=800" width="652" height="321" /></a></p> <p>Analizaremos 3 formas de comunicar formularios y enviar datos entre ellos:</p> <ul> <li>Usando Interfaces </li> <li>Usando eventos </li> <li>Usando el evento FormClosed </li> </ul> <p> </p> <p><strong>Usando interfaces</strong> <hr /></p> <p>Una interfaz permite definir un contrato entre clases, permitiendo que una invoque la funcionalidad de cualquier otra que respete ese contrato definido.</p> <p>Recordemos que los formularios en definitiva no son mas que clases que se instancian por lo que puede aplicarse los mismos conceptos de POO (Programación orientada a objetos).</p> <p>La implementación de esta técnica se encuentra en la búsqueda de un proveedor en el form de edición de los productos.</p> <p><a href="http://lh3.ggpht.com/-4XP7Evxw6Ss/U35tzU37GvI/AAAAAAAABu8/ItRkJ9Zs39M/s1600-h/image12.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-qhvCSF_GV2U/U35t0DvO-iI/AAAAAAAABvE/tGEPnkZV-FQ/image_thumb8.png?imgmax=800" width="709" height="348" /></a></p> <p>El primer paso será definir la interfaz</p> <p><a href="http://lh3.ggpht.com/-uThyBwsk-iA/U35t0mc-38I/AAAAAAAABvM/G871gpio-6c/s1600-h/image%25255B7%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-tEKznGV-6To/U35t2oDZNLI/AAAAAAAABvU/2WRpU3NbHCA/image_thumb%25255B4%25255D.png?imgmax=800" width="323" height="102" /></a></p> <p>siendo implementada por el formulario padre receptora de la información seleccionada por el usuario</p> <p><a href="http://lh4.ggpht.com/-BRakaHyWZzI/U35t3S30byI/AAAAAAAABvc/WOgP-lm0FCk/s1600-h/SNAGHTML5a01d2356.png" target="_blank"><img title="SNAGHTML5a01d235" style="display: inline" alt="SNAGHTML5a01d235" src="http://lh6.ggpht.com/-eH0QkRugtgQ/U35t5CLbaVI/AAAAAAAABvk/QG05MFkRvNY/SNAGHTML5a01d235_thumb3.png?imgmax=800" width="529" height="373" /></a></p> <p>El form padre será quien instancie el form hijo pasando su instancia por parámetro en el constructor, además de algún otro valor adicional de ser necesario, como en este caso un filtro para limitar los ítems visualizados en el grid de selección.</p> <p> <a href="http://lh4.ggpht.com/-yj8kMpo5Bu4/U35t543KBXI/AAAAAAAABvs/XjPRYNGN450/s1600-h/image18.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-xjFD1GuMLzs/U35t6oSwRHI/AAAAAAAABv0/61kMPyW_zps/image_thumb12.png?imgmax=800" width="596" height="115" /></a></p> <p> </p> <p>Desde el form hijo (frmFindSupplier) se define el constructor que recibirá los parámetro</p> <p><a href="http://lh4.ggpht.com/-fkzIxs3FPhw/U35t7dYAeNI/AAAAAAAABv8/anyCOmbEXhI/s1600-h/SNAGHTML5a09bacf6.png" target="_blank"><img title="SNAGHTML5a09bacf" style="display: inline" alt="SNAGHTML5a09bacf" src="http://lh4.ggpht.com/-u-u1tFO3hFw/U35t8jVjJVI/AAAAAAAABwE/NPor64s40vI/SNAGHTML5a09bacf_thumb3.png?imgmax=800" width="567" height="338" /></a></p> <p>La variable “caller” contendrá la instancia del form padre que implementa la interfaz, mientras que “_supplierName” tendrá el input del usuario para filtrar las entidades.</p> <p>Al seleccionar un ítem del grid se dará por seleccionada la entidad y se enviara la acción al form padre a través de la instancia invocando el método definido en la interfaz.</p> <p><a href="http://lh6.ggpht.com/-2PCpSqilhxc/U35t9YTSpUI/AAAAAAAABwM/EjeL9Ql3JuQ/s1600-h/image31.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-BWA_058mcYM/U35t-b0QEcI/AAAAAAAABwU/o2k8a5pozRE/image_thumb21.png?imgmax=800" width="649" height="291" /></a></p> <p>Primero se valida que exista selección en el grid, se recupera la entidad seleccionada pasándola como parametro al método Selected() definido en la interfaz, por ultimo se cierra el formulario. El form padre recibe el valor seleccionado y actualiza los controles o entidades que hagan falta.</p> <p>La secuencia completa quedaría de esta forma</p> <p><a href="http://lh5.ggpht.com/-YOeo0pX7fG4/U35t_L8CIlI/AAAAAAAABwc/EjfEkJoXn4g/s1600-h/SNAGHTML5b152bbf6.png" target="_blank"><img title="SNAGHTML5b152bbf" style="display: inline" alt="SNAGHTML5b152bbf" src="http://lh5.ggpht.com/-7OV4YCj0-2I/U35t__nH_4I/AAAAAAAABwk/_RcOcTPSYeY/SNAGHTML5b152bbf_thumb3.png?imgmax=800" width="792" height="396" /></a></p> <ol> <li>El form padre (frmEditProductos) implementa la interfaz. </li> <li>En el botón de búsqueda se crea la instancia del form hijo (frmFindSupplier), pasando como parámetro en el constructor la instancia del forma padre (utilizando <em>this</em>) </li> <li>El form hijo recibe en el constructor los valores por parámetro </li> <li>Se selecciona un ítem del grid y se invoca al método de la interfaz </li> <li>El form padre recibe la selección y actualiza los datos </li> </ol> <p>Como ventaja se puede mencionar que cualquier form que implemente la interfaz podrá reutilizar la funcionalidad de búsqueda implementada al no quedar acoplados los formularios que interactuan.</p> <p> </p> <p><strong>Usando eventos</strong> <hr /></p> <p>Permite enviar una acción directa al form que se subscriba al evento. </p> <p>El ejemplo va a estar representado en la comunicación del form de edición de productos y la búsqueda de categorías.</p> <p><a href="http://lh6.ggpht.com/-QeNR4bWQ7ZI/U35uA-S4B9I/AAAAAAAABws/fAGXaSI4lac/s1600-h/image5.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-6952R5kpEd8/U35uB6qg0DI/AAAAAAAABw0/5a-5vMmJYeM/image_thumb3%25255B1%25255D.png?imgmax=800" width="659" height="319" /></a></p> <p>Partamos analizando la implementación del form hijo (frmFindCategory), este expone el evento, que es lanzado cuando se selecciona una categoría del grid</p> <p><a href="http://lh5.ggpht.com/-kFIb-MDwbcY/U35uCZ01pZI/AAAAAAAABw8/FcI2QmYCgAI/s1600-h/SNAGHTML5a76cd36.png"><img title="SNAGHTML5a76cd3" style="display: inline" alt="SNAGHTML5a76cd3" src="http://lh6.ggpht.com/-93sjJ4sry-0/U35uG76YHuI/AAAAAAAABxE/pAwX541F-m4/SNAGHTML5a76cd3_thumb3.png?imgmax=800" width="630" height="542" /></a></p> <p>Para definir datos específicos en el intercambio de información se crea una clase que hereda de EventArgs </p> <p><a href="http://lh3.ggpht.com/-yyvqFAFTazQ/U35uHRFnS7I/AAAAAAAABxI/S4x5zO2NAmc/s1600-h/image15.png"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-owhm0EtjRYQ/U35uH55_mrI/AAAAAAAABxQ/d1olcK6skGk/image_thumb9.png?imgmax=800" width="347" height="78" /></a></p> <p> </p> <p>Desde el form padre (frmEditProductos) simplemente se debe adjuntar al evento la instancia del form hijo</p> <p><a href="http://lh3.ggpht.com/-MfM4bHhSQlg/U35uIlv-eLI/AAAAAAAABxc/-bLxWkvb2Lg/s1600-h/image16.png"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-1kROO0Tp4L8/U35uJDNyJlI/AAAAAAAABxk/G1dTd9VDQ4U/image_thumb10.png?imgmax=800" width="603" height="299" /></a></p> <p> </p> <p>Repasemos como seria la ejecución en secuencia</p> <p><a href="http://lh4.ggpht.com/-w6b3qQ61Sk4/U35uKIZV9UI/AAAAAAAABxs/QxMm1HqAYT0/s1600-h/SNAGHTML5c710fe6.png" target="_blank"><img title="SNAGHTML5c710fe" style="display: inline" alt="SNAGHTML5c710fe" src="http://lh4.ggpht.com/-NLs6rvznmVw/U35uK9FqD-I/AAAAAAAABx0/onXFpgbxVy0/SNAGHTML5c710fe_thumb3.png?imgmax=800" width="780" height="440" /></a></p> <p> </p> <ol> <li>El form hijo expone el evento, utilizado para informar la selección a quien lo haya invocado.</li> <li>Se define una clase que hereda de EventArgs para poder pasar información adicional como argumento del evento. </li> <li>El form padre que realiza la búsqueda crea la instancia del forma hijo y se adjunta al evento, además crea el método que recibirá la acción. </li> <li>Cuando se selecciona un ítem del grid se valida si alguien esta adjunto en el evento y se invoca. </li> </ol> <p> </p> <p><strong>Actualizar los datos al cerrar el form (evento FormClosed)</strong></p> <hr /> <p>Para actualizar un grid luego de una acción en otro form podría utilizarse eventos existentes, como ser el FormClosed, con este evento se puede realizar una acción al cerrar un form </p> <p><a href="http://lh6.ggpht.com/-roTsYTDvjIw/U35uLt0tSMI/AAAAAAAABx8/s8vtzcANhro/s1600-h/image6%25255B1%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-H100brJDpP0/U35uMQBq1OI/AAAAAAAAByA/j_mw_WLtkkc/image_thumb3.png?imgmax=800" width="684" height="353" /></a></p> <p> </p> <p>Analicemos los pasos</p> <p><a href="http://lh5.ggpht.com/-eRDZ3z3iHPo/U35uNXZlhhI/AAAAAAAAByM/Lon5tpk3w4k/s1600-h/SNAGHTMLaeed38d6.png" target="_blank"><img title="SNAGHTMLaeed38d" style="display: inline" alt="SNAGHTMLaeed38d" src="http://lh4.ggpht.com/-1fej4PYSMis/U35uOBdlDiI/AAAAAAAAByU/QJfx-wLgAp0/SNAGHTMLaeed38d_thumb3.png?imgmax=800" width="784" height="256" /></a></p> <p> </p> <ol> <li>En el form padre (frmAdminProducts) instancia el form hijo (frmEditProductos) y se adjunta el evento de cierre del form</li> <li>Cuando se presiona el boton de aceptar o cancelar se devuelve el DialogResult cerrando el form, esto lanza el evento FormClosed</li> <li>Desde el form padre se atrapa el evento y se realiza la recarga de los datos del grid</li> </ol> <p> </p> <p><strong>Descarga</strong> <hr /></p> <p>El articulo fue desarrollando usando Visual Studio 2012, la base de datos es Sql Compact (.sdf)</p> <p>Durante la compilación se descargaran las dependencias por medio de Nuget.</p> <p> <br clear="all" /> <table cellspacing="0" cellpadding="2" width="400" border="0"><tbody> <tr> <td valign="top" width="200">[C#] <br /><iframe height="120" src="https://onedrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21476&authkey=AJ0oYIVWDzHSzlY" frameborder="0" width="98" scrolling="no"></iframe></td> <td valign="top" width="200"> </td> </tr> </tbody></table></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com38tag:blogger.com,1999:blog-7361892840793499128.post-27339929620078349832013-12-28T15:28:00.001-08:002013-12-28T15:28:14.491-08:00[ASP.NET MVC] Carga DropDownList dependientes - Usando un único action<p> </p> <p><strong>Introducción <hr /></strong></p> <p>Este articulo propone una versión diferente a la planteada en el anterior</p> <p><a href="http://ltuttini.blogspot.com.ar/2013/12/aspnet-mvc-carga-dropdownlist.html">[ASP.NET MVC] Carga DropDownList dependientes</a></p> <p>En este caso definiremos un único action para recibir la selección que realice el usuario.</p> <p>La funcionalidad de la pantalla se conserva idéntico al articulo anterior, al igual que el modelo de datos.</p> <p> </p> <p><strong>Modelo de Vista <hr /></strong></p> <p>Para el modelo se define una clase que recibirá las propiedades con la selección del usuario, además de propiedades que permitirán cargar los controles de listas en la view</p> <p><a href="http://lh6.ggpht.com/-dUFvqXnZBiw/Ur9eXISSjvI/AAAAAAAABm4/2xSXWd1yw2A/s1600-h/image%25255B17%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-m0IfJAPWxT8/Ur9eYAwYUFI/AAAAAAAABnA/zJ-d-bJiuOE/image_thumb%25255B11%25255D.png?imgmax=800" width="542" height="415" /></a></p> <p> </p> <p><strong>Definición de la View <hr /></strong></p> <p>La vista esta formada por un solo Form, el cual realiza el post al action Index() el cual se encuantra marcado con el atributo [HttpPost]</p> <p><a href="http://lh5.ggpht.com/-QlxqVUfXX7o/Ur9eZAilgNI/AAAAAAAABnI/NhS6Pu5a4UY/s1600-h/SNAGHTML19f9df10%25255B8%25255D.png" target="_blank"><img title="SNAGHTML19f9df10" style="display: inline" alt="SNAGHTML19f9df10" src="http://lh6.ggpht.com/-39xaY_sa8wg/Ur9eaPYybKI/AAAAAAAABnQ/6Hcqbs8MPyc/SNAGHTML19f9df10_thumb%25255B5%25255D.png?imgmax=800" width="710" height="763" /></a></p> <p> </p> <p>Tanto si se selecciona un ítem combo, o se presiona el botón del submit (el cual selecciona los empleados) todas las acciones invocan al único action</p> <p>A diferencia del articulo anterior ya no se requieren de hidden para conservar la selección de la acción anterior.</p> <p> </p> <p><strong>Definición del action <hr /></strong></p> <p>El action implementar bastante mas lógica que la usada en el articulo anterior, ya que deberá determinar que propiedades vienen con datos y cargar las listas en consecuencia.</p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:9cf1df38-d663-4d22-a0dc-4e004779df8d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[HttpPost]
public ActionResult Index(EmployeeModel model)
{
//se carga la lista de regiones
var regionList = regionRepository.GetAll();
List<Territory> territoryList = null;
List<Employee> employeeList = null;
//si hay una region seleccionada
if (model.RegionId != null)
{
//se carga la lista de territorios
territoryList = territoryRepository.Filter(x => x.RegionID == model.RegionId);
//si hay un territorio seleccionado
if (!string.IsNullOrEmpty(model.TerritoryId))
{
//se recupera el territorio, para poder obtener los empleados asociados
employeeList = territoryRepository.GetEmployees(model.TerritoryId);
}
}
model.RegionList = new SelectList(regionList, "RegionID", "Description");
if (territoryList != null && territoryList.Count > 0)
{
model.TerritoryList = new SelectList(territoryList, "TerritoryID", "Description");
}
if (employeeList != null && employeeList.Count > 0)
{
model.EmployeeList = new SelectList(employeeList, "EmployeeID", "FullName");
}
return View("Index", model);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Si se selecciona una región se recibirá el valor en el parámetro del modelo</p>
<p><a href="http://lh6.ggpht.com/-2Rm6vB5tN7w/Ur9eayVbAzI/AAAAAAAABnY/VWN3NMurUVU/s1600-h/SNAGHTML1a04b6aa%25255B6%25255D.png" target="_blank"><img title="SNAGHTML1a04b6aa" style="display: inline" alt="SNAGHTML1a04b6aa" src="http://lh3.ggpht.com/-7h04x3lEVYo/Ur9ecF4n4uI/AAAAAAAABng/Vef2V-lXUiw/SNAGHTML1a04b6aa_thumb%25255B3%25255D.png?imgmax=800" width="661" height="392" /></a></p>
<p> </p>
<p>Si luego se selecciona un territorio</p>
<p><a href="http://lh3.ggpht.com/-9KybaAoNE4M/Ur9ec-6I87I/AAAAAAAABno/8mZJDZ5ShD4/s1600-h/SNAGHTML1a08bf80%25255B6%25255D.png" target="_blank"><img title="SNAGHTML1a08bf80" style="display: inline" alt="SNAGHTML1a08bf80" src="http://lh5.ggpht.com/-57P9pWSBeMU/Ur9ed_G2RCI/AAAAAAAABnw/37qh_pwbN3k/SNAGHTML1a08bf80_thumb%25255B3%25255D.png?imgmax=800" width="661" height="259" /></a></p>
<p> </p>
<p>Y al marcar empleados sobre el listbox y presionar el botón de submit se carga la lista</p>
<p><a href="http://lh3.ggpht.com/-uE16t_ms8lw/Ur9eeZ1S-9I/AAAAAAAABn0/GsmJ2vy6Bzw/s1600-h/SNAGHTML1a0b84e0%25255B6%25255D.png" target="_blank"><img title="SNAGHTML1a0b84e0" style="display: inline" alt="SNAGHTML1a0b84e0" src="http://lh6.ggpht.com/-9hy_vc4Nf9c/Ur9efqQwxdI/AAAAAAAABoA/kwEoWXQdY9w/SNAGHTML1a0b84e0_thumb%25255B3%25255D.png?imgmax=800" width="664" height="246" /></a></p>
<p> </p>
<p>En cada invocación al acción será necesario volver a cargar las listas del model utilizadas para definir los ítems de los controles la la view, ya que estas no participan en el proceso de binding donde son asignados los valores que se envían en al post a las propiedades del parámetro del action.</p>
<p> </p>
<p><strong>Model Binding
<hr /></strong></p>
<p>Para entender como se produce la asignación de la selección de los controles con las propiedades del modelo es necesario evaluar el html</p>
<p>Si las propiedades “name” de los controles que se renderizan en el html coinciden con los nombres de las propiedades de la clase definida como modelo la asignación del valor es automáticamente.</p>
<p> </p>
<p><a href="http://lh5.ggpht.com/-AeGZnTvwoV4/Ur9egvYze0I/AAAAAAAABoI/zghDoE5XiXQ/s1600-h/SNAGHTML1a315f77%25255B7%25255D.png" target="_blank"><img title="SNAGHTML1a315f77" style="display: inline" alt="SNAGHTML1a315f77" src="http://lh5.ggpht.com/-cLaprGQAu1w/Ur9eh6G-_pI/AAAAAAAABoQ/IYCw0Z9HOLM/SNAGHTML1a315f77_thumb%25255B4%25255D.png?imgmax=800" width="655" height="566" /></a></p>
<p>si algún control no llegara a tener esta coincidencia se puede redefinir el name utilizando</p>
<p><a href="http://lh5.ggpht.com/-0PyPd1WoKj8/Ur9eiU1okKI/AAAAAAAABoU/WP6gl2k5hQE/s1600-h/image%25255B33%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-MiChy-qSCtU/Ur9ejA8R5rI/AAAAAAAABog/5m1f9R8M8BY/image_thumb%25255B21%25255D.png?imgmax=800" width="643" height="88" /></a></p>
<p> </p>
<p><strong>Código</strong>
<hr /></p>
<p> </p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21445&authkey=AJSPfn6V5FsUV9M" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com18tag:blogger.com,1999:blog-7361892840793499128.post-29874858359401331362013-12-27T19:50:00.001-08:002013-12-27T19:50:09.407-08:00[ASP.NET MVC] Carga DropDownList dependientes (cascada o secuencial)<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Es muy común en las aplicaciones necesitar de controles que dependan unos de otros para cargar los ítems que serán desplegados al usuario.</p> <p>En este articulo explicare como llevar a cabo esta tarea implementando la carga en cascada de combos y listas, además poder tomar en cada post la selección que realiza el usuario.</p> <p>El ejemplo permitirá seleccionar una región la cual será utilizada como filtro para cargar los territorios, este ultimo cargar una lista de empleados, el usuario seleccionara uno o mas de la lista. </p> <p> </p> <p><a href="http://lh6.ggpht.com/--nihSiQwm04/Ur5KO3DU2_I/AAAAAAAABkc/QGRPaOR3Se0/s1600-h/image%25255B6%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-LqirxBO0Od4/Ur5KPlYk0lI/AAAAAAAABkk/4dP0VofG1MA/image_thumb%25255B3%25255D.png?imgmax=800" width="398" height="351" /></a></p> <p> </p> <p><strong>Modelo de datos <hr /></strong></p> <p>Haremos uso del siguiente modelo de datos el cual define las clases que serán mapeadas a las tablas utilizando Entity Framework</p> <p>Vemos como interactúan las entidades para permitir la asociaciones que las vinculan.</p> <p><a href="http://lh6.ggpht.com/-X81MsDS38h8/Ur5KQZJWYWI/AAAAAAAABks/uYgAfCGIMb4/s1600-h/image6.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-BV1RptLRVLM/Ur5KRLDbdHI/AAAAAAAABk0/6nj5tICGfoM/image_thumb4.png?imgmax=800" width="678" height="400" /></a></p> <p> </p> <p><strong>Modelo de Vista <hr /></strong></p> <p>Para poder interactuar con al view de mvc creamos una clase que actuara de modelo, esta nos permitirá definir los ítems de las listas que desplegaremos al usuario.</p> <p>En el modelo tendremos propiedades simples que contendrán la selección del usuario y también lista que nos permitirán cargar los dropdownlist y listbox que presentaremos al usuario.</p> <p> </p> <p><a href="http://lh4.ggpht.com/-Wx9wMgNJmGE/Ur5KR3CWw5I/AAAAAAAABk8/g-8YUgWAiWk/s1600-h/image%25255B13%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-qSS9pTOmTzo/Ur5KSiXCH2I/AAAAAAAABlE/NteW1_6oY6Y/image_thumb%25255B8%25255D.png?imgmax=800" width="484" height="411" /></a></p> <p>Las propiedades de lista se definen del tipo SelectList, de esta forma desde el controlador indicamos cuales serán las propiedades que usara la lista como value y display en el dropdownlist </p> <p> </p> <p><strong>Lista de Regiones <hr /></strong></p> <p>Al iniciar la aplicación el primer controlador que se ejecuta es el Index(), en este invocamos al repositorio para obtener la lista de regiones</p> <p><a href="http://lh5.ggpht.com/-20HwSvTZB04/Ur5KTFduACI/AAAAAAAABlM/XoOSxrKX6QI/s1600-h/image%25255B19%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-XMRVSuikA_A/Ur5KT1jO0AI/AAAAAAAABlU/dLXbjzK3KQQ/image_thumb%25255B12%25255D.png?imgmax=800" width="586" height="168" /></a></p> <p> </p> <p>Creamos una instancia de la clase Model que el view requiere, a esta le asignamos la lista de regiones que mostrara en el combo</p> <p>En la view se carga el dropdownlist y se controla con la ayuda de jquery cuando el usuario cambia la selección, lo cual produce el post del form. </p> <p>En la imagen podemos ver como se relacionan los id del dropdownlist y del form con el evento change() de jquery para generar el submit al action del controlador</p> <p> </p> <p><a href="http://lh5.ggpht.com/-qyZ6SZpC80w/Ur5KUvrshJI/AAAAAAAABlc/7rZ3lLZiolY/s1600-h/SNAGHTML146b09bb%25255B7%25255D.png" target="_blank"><img title="SNAGHTML146b09bb" style="display: inline" alt="SNAGHTML146b09bb" src="http://lh6.ggpht.com/-1Cl7tSQcvfU/Ur5KV154NnI/AAAAAAAABlk/Cjkh0l0zjlE/SNAGHTML146b09bb_thumb%25255B4%25255D.png?imgmax=800" width="585" height="434" /></a></p> <p> </p> <p><strong>Lista de Territorios <hr /></strong></p> <p>Al seleccionar una región se invoca el action definido en el BeginForm()</p> <p><a href="http://lh6.ggpht.com/-otdTg-8jbnw/Ur5KWuZNZ_I/AAAAAAAABls/0Nc2KMc-oUs/s1600-h/SNAGHTML14776cd1%25255B6%25255D.png" target="_blank"><img title="SNAGHTML14776cd1" style="display: inline" alt="SNAGHTML14776cd1" src="http://lh6.ggpht.com/-LhvdT2DLE38/Ur5KXjz9ANI/AAAAAAAABl0/Cf81n2E43ek/SNAGHTML14776cd1_thumb%25255B3%25255D.png?imgmax=800" width="621" height="391" /></a> </p> <p>Si hacemos coincidir el name del control dropdownlist con el del parámetro del action este recibirá el valor. Usaremos la selección para filtrar la lista de territorios.</p> <p> </p> <p><strong>Lista de Empleados <hr /></strong></p> <p>Al seleccionar un ítem del combo de territorios se dispara el submit del forma que invoca el action del controlador, en este caso se pasaran como argumento el id de la región y del territorio para poder filtrar los datos y cargar las listas</p> <p> </p> <p><a href="http://lh5.ggpht.com/-w0sLBxaQPHo/Ur5KYBi-kYI/AAAAAAAABl8/2QiZxs6DOag/s1600-h/SNAGHTML14cd3b57%25255B7%25255D.png" target="_blank"><img title="SNAGHTML14cd3b57" style="display: inline" alt="SNAGHTML14cd3b57" src="http://lh3.ggpht.com/-kJs_DcFcf3c/Ur5KZYELf9I/AAAAAAAABmE/K6HM6lvP3Y4/SNAGHTML14cd3b57_thumb%25255B4%25255D.png?imgmax=800" width="651" height="457" /></a></p> <p>Seguramente se habrá notado la definición de controles Hidden que permiten enviar en cada submit del form los datos seleccionados en una acción anterior</p> <p> </p> <p><strong>Selección de empleados <hr /></strong></p> <p>Cuando se selecciona los empleados de la lista y se presiona el botón se realiza el submit enviando la la selección del listbox al action </p> <p><a href="http://lh6.ggpht.com/-mJpJBrsMvlM/Ur5KaGlvGpI/AAAAAAAABmM/78UBGGffwO0/s1600-h/SNAGHTML14d57876%25255B7%25255D.png" target="_blank"><img title="SNAGHTML14d57876" style="display: inline" alt="SNAGHTML14d57876" src="http://lh3.ggpht.com/-AEbBjditrC0/Ur5Kb-q9sUI/AAAAAAAABmU/Ns4hyDUtP_E/SNAGHTML14d57876_thumb%25255B4%25255D.png?imgmax=800" width="719" height="627" /></a></p> <p>En cada invocación a los action se vuelve a crear una instancia del model asignando la selección y cargando las listas que requiere el view </p> <p> </p> <p><strong>Código <hr /></strong></p> <p>Se utilizo Visual Studio 2012, la base de datos de encuentra en la carpeta App_Data, se utilizo Sql Server Express 2012</p> <table cellspacing="0" cellpadding="2" width="400" border="0"><tbody> <tr> <td valign="top" width="200">[C#] <br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21443&authkey=AK8QfBBVx1IloX4" frameborder="0" width="98" scrolling="no"></iframe></td> <td valign="top" width="200"> </td> </tr> </tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com67tag:blogger.com,1999:blog-7361892840793499128.post-44452920557286754452013-11-03T22:33:00.001-08:002013-11-24T03:53:52.863-08:00[ASP.NET MVC] WebGrid Filtrado por DropDownList<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>En el articulo aprenderemos :</p> <ul> <li>como capturar la selección de un DropDownList</li> <li>filtrar una query usando Entity Framework</li> <li>desplegar los datos en un WebGrid</li> </ul> <p>La idea es listas en un dropdownlist los clientes (customers) y al seleccionar alguno, listar las ordenes de compra realizadas.</p> <p><a href="http://lh6.ggpht.com/-L4T5or9iohc/UpHopEnMFXI/AAAAAAAABhc/vqZitCXuCGQ/s1600-h/image%25255B24%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-zvucLpjg-xM/UpHopzhx7PI/AAAAAAAABhk/CfUSMonsN6s/image_thumb%25255B16%25255D.png?imgmax=800" width="578" height="263" /></a></p> <p> </p> <p><strong>Modelo de Dominio <hr /></strong></p> <p>Empezaremos analizando el dominio de entidades que usaremos en el ejemplo.</p> <p><a href="http://lh6.ggpht.com/-hfi0BgoK_WY/UpHoqQT3R-I/AAAAAAAABhs/OnaPYQVolk8/s1600-h/image%25255B12%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-A_AqbJop5Wc/UpHorB4yh2I/AAAAAAAABh0/cVfU1xAGvaw/image_thumb%25255B8%25255D.png?imgmax=800" width="707" height="393" /></a></p> <p>La entidad Orden tiene relación con el cliente (customer) y un empleado (employee), usaremos el primero para filtrar mientras que el segundo será información adicional que se mostrara en una columna del grid</p> <p> </p> <p><strong>Definición de la persistencia <hr /></strong></p> <p>El acceso a los datos lo realizaremos con al ayuda de Entity Framework, por lo que deberemos definir el DbContext y las clases de mapping para cada entidad</p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:84dabada-76e3-4cce-bd6e-f7b139afa74d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
Database.SetInitializer<NorthWindContext>(null);
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Customer> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CustomerMap());
modelBuilder.Configurations.Add(new EmployeeMap());
modelBuilder.Configurations.Add(new OrderMap());
modelBuilder.Configurations.Add(new LocationComplexMap());
base.OnModelCreating(modelBuilder);
}
}
public class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasKey(x => x.CustomerID);
Property(x => x.CustomerID)
.HasColumnType("nchar")
.HasMaxLength(5);
Property(x => x.CompanyName)
.HasMaxLength(40)
.IsRequired();
Property(x => x.ContactName)
.HasMaxLength(30);
Property(x => x.ContactTitle)
.HasMaxLength(30);
}
}
public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
HasKey(x => x.EmployeeID);
Property(x => x.LastName)
.HasMaxLength(20)
.IsRequired(); ;
Property(x => x.FirstName)
.HasMaxLength(10)
.IsRequired();
Ignore(x => x.FullName);
}
}
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
HasKey(x => x.OrderID);
Property(x => x.CustomerID)
.HasColumnType("nchar")
.HasMaxLength(5)
.IsOptional();
HasOptional(x => x.Customer)
.WithMany()
.HasForeignKey(x => x.CustomerID);
HasOptional(x => x.Employee)
.WithMany()
.HasForeignKey(x => x.EmployeeID);
Property(x => x.ShipName)
.HasMaxLength(40);
Property(x => x.ShipAddress)
.HasMaxLength(60);
}
}
public class LocationComplexMap : EntityTypeConfiguration<Localization>
{
public LocationComplexMap()
{
Property(x => x.Address).HasColumnName("Address").HasMaxLength(60);
Property(x => x.City).HasColumnName("City").HasMaxLength(15);
Property(x => x.Region).HasColumnName("Region").HasMaxLength(15);
Property(x => x.PostalCode).HasColumnName("PostalCode").HasMaxLength(10);
Property(x => x.Country).HasColumnName("Country").HasMaxLength(15);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>En este caso estamos utilizando una db existente por lo tanto importante definir la línea:</p>
<p><em> Database.SetInitializer<NorthWindContext>(null);</em></p>
<p>de esta forma indicamos que no se debe crear la db cuando ejecutemos la aplicacion</p>
<p> </p>
<p><strong>Definir datos del dropdownlist
<hr /></strong></p>
<p>Para la vista definiremos un modelo que nos permita especificar los datos que mostraremos al usuario.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:1ae761df-7dd8-4dd5-9cdc-3bd8dfc97561" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class CustomerOrdersModel
{
public SelectList CustomerList { get; set; }
public List<Order> OrderList { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Es por eso que encontraremos dos listas, una para cargar los ítems del combo y la segunda para las ordenes del grid.</p>
<p>Al ingresar al action Index(), el cual responde a los pedidos GET, del controlador debemos cargar la lista del combo, nos ayudaremos con el repositorio para esta tarea.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:18d9d609-94c4-4284-a982-52afec9d48fc" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public ActionResult Index()
{
CustomerOrdersModel model = new CustomerOrdersModel()
{
CustomerList = new SelectList(repository.GetAllWithoutSelection(), "CustomerID", "CompanyName")
};
return View(model);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>la vista hará uso de las propiedades del modelo para cargar el dropdownlist</p>
<p><a href="http://lh6.ggpht.com/-Lb7TNxCKeTY/UpHorzoN4JI/AAAAAAAABh8/Mv7UWpZ8mA0/s1600-h/SNAGHTML978badb8%25255B7%25255D.png" target="_blank"><img title="SNAGHTML978badb8" style="display: inline" alt="SNAGHTML978badb8" src="http://lh3.ggpht.com/-QaJgbVPNsJM/UpHosfZA4tI/AAAAAAAABiE/IBW4LCvfWSg/SNAGHTML978badb8_thumb%25255B4%25255D.png?imgmax=800" width="731" height="350" /></a></p>
<p> </p>
<p><strong>Capturar selección dropdownlist
<hr /></strong></p>
<p>La captura de la selección puede realizarse de varias formas, pero no debe perderse de vista que es la propiedad “name” de los controles html los que participan en el post que se realiza al servidor, no hay que confundirse con la propiedad “id” esta solo es útil para manipular el control en el cliente usando jquery.</p>
<p>Para capturar la acción del usuario al cambiar la selección en el dropdownlist nos ayudamos de jquery, para poder definir el selector definimos un id en el tag <form></p>
<p><a href="http://lh4.ggpht.com/-VnWe4_sz7kk/UpHos-2jvYI/AAAAAAAABiM/0-qeewVsjaQ/s1600-h/SNAGHTML97918cef%25255B6%25255D.png" target="_blank"><img title="SNAGHTML97918cef" style="display: inline" alt="SNAGHTML97918cef" src="http://lh5.ggpht.com/-ul3XW8_gEYA/UpHotgMpWqI/AAAAAAAABiU/z7HFGWUGfjI/SNAGHTML97918cef_thumb%25255B3%25255D.png?imgmax=800" width="731" height="312" /></a></p>
<p>Es en ese momento cuando forzamos un submit para invocar a un action concreto</p>
<p><a href="http://lh4.ggpht.com/-f3CktQCF-JA/UpHouFv2hfI/AAAAAAAABiY/op9rjiK_Fcw/s1600-h/image%25255B17%25255D.png"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-i7b3ZCWp3bo/UpHoutXU9LI/AAAAAAAABik/RRcytXfRVmg/image_thumb%25255B11%25255D.png?imgmax=800" width="650" height="144" /></a></p>
<p>en el controlador encontraremos </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:e46288c5-53ba-4e07-a3cb-0a2cefb0e534" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[HttpPost]
public ActionResult Index(string CustomerList)
{
//
// usamos el metodo del repositorio el cual nos permite recuperar las odenes de compra del cliente
// se incluye en esta la informacion del cliente y el empleaod que realizo la venta
//
var orders = orderRepository.Filter(x => x.CustomerID == CustomerList,
new List<Expression<Func<Entities.Order,object>>>() { x=>x.Employee, x=>x.Customer });
CustomerOrdersModel model = new CustomerOrdersModel()
{
CustomerList = new SelectList(repository.GetAllWithoutSelection(), "CustomerID", "CompanyName"),
OrderList = orders
};
return View(model);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>El nombre del parámetro debe coincidir con la propiedad “name” del control para poder tomar el dato, en este caso hemos dejado el valor por defecto que crear asp.net mvc, pero podríamos redefinirlo.</p>
<p><a href="http://lh5.ggpht.com/-PkYdvAuYDuQ/UpHovP7f2XI/AAAAAAAABis/lPErdTnizko/s1600-h/SNAGHTML97af81c9%25255B7%25255D.png" target="_blank"><img title="SNAGHTML97af81c9" style="display: inline" alt="SNAGHTML97af81c9" src="http://lh4.ggpht.com/-2-XSAwr9mmA/UpHov8ondlI/AAAAAAAABi0/7AuqsRYWHZM/SNAGHTML97af81c9_thumb%25255B4%25255D.png?imgmax=800" width="719" height="252" /></a></p>
<p>Si en la vista cambiamos la propiedad “name” debemos también hacerlo en el parámetro del action.</p>
<p><a href="http://lh3.ggpht.com/-Z1AIex-rbIM/UpHowevY8CI/AAAAAAAABi8/YOvwMFonVhc/s1600-h/SNAGHTML97a1f0d0%25255B6%25255D.png" target="_blank"><img title="SNAGHTML97a1f0d0" style="display: inline" alt="SNAGHTML97a1f0d0" src="http://lh5.ggpht.com/-_oLGxMDXcVY/UpHoxL2zytI/AAAAAAAABjE/xZuKmJ7HMUc/SNAGHTML97a1f0d0_thumb%25255B3%25255D.png?imgmax=800" width="709" height="192" /></a></p>
<p> </p>
<p> </p>
<p><strong>Definición del WebGrid
<hr /></strong></p>
<p>El post al action devolverá un nuevo modelo con la lista de ordenes cargada</p>
<p><a href="http://lh6.ggpht.com/-xn4nqWUhh48/UpHoxlz2wkI/AAAAAAAABjM/nvferllei6o/s1600-h/image%25255B31%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-Wp7-_-SttJI/UpHoyZJFlVI/AAAAAAAABjU/Yd8uE0WozHY/image_thumb%25255B21%25255D.png?imgmax=800" width="599" height="414" /></a></p>
<p>En la definición del grid es cuando indicamos que propiedad del modelo define los datos </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:344f0b74-6720-477e-bae0-ed95eb650d7b" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var grid = new WebGrid(Model.OrderList, canPage: false, canSort: false);</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>luego se define el lugar donde ira el grid y que columnas se mostrara</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a0056310-ed18-45d2-a0dc-48bba4a9af30" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">@if (Model.OrderList != null)
{
@grid.GetHtml(
tableStyle: "webgrid-table",
headerStyle: "webgrid-head",
alternatingRowStyle: "webgrid-alternating-row",
selectedRowStyle: "webgrid-selected-row",
columns: grid.Columns(
grid.Column(columnName: "Employee.FullName", header: "Empleado", style: "webgrid-column-employee"),
grid.Column(columnName: "OrderDate", header: "Fecha Pedido", format: x=>x.OrderDate.ToString("dd/MM/yyyy")),
grid.Column(columnName: "RequiredDate", header: "Fecha Requisito", format: x=>x.RequiredDate.ToString("dd/MM/yyyy")),
grid.Column(columnName: "ShippedDate", header: "Fecha Envio", format: x=>x.ShippedDate != null ? x.ShippedDate.ToString("dd/MM/yyyy") : ""),
grid.Column(columnName: "ShipName", header: "Nombre Envio"),
grid.Column(columnName: "ShipAddress", header: "Direccion Envio")
))
}
else
{
<p>No hay datos</p>
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>se podría resaltar como se especifica una columna cuando la entidad esta relacionada con otra</p>
<p><em>grid.Column(columnName: "<font color="#ff0000">Employee.FullName</font>", header: "Empleado", style: "webgrid-column-employee"),</em></p>
<p>al necesitar el nombre del empleado se realiza por medio de la relación </p>
<p>También se definió formato para las columnas fecha</p>
<p><em>grid.Column(columnName: "ShippedDate", header: "Fecha Envio", <font color="#ff0000">format: x=>x.ShippedDate != null ? x.ShippedDate.ToString("dd/MM/yyyy") : ""</font>),</em></p>
<p>no solo indicando la representación de la fecha sino también validando cuando esta pueda tener un valor nulo</p>
<p> </p>
<p><strong>WebGrid referencia en asp.net mvc
<hr /></strong></p>
<p>Según el tipo de proyecto que crees en el Visual Studio puede que no tengas acceso al WebGrid, si este es el caso deberá agregar la referencia a la librería, para esta tarea NuGet nos dará una mano</p>
<p>Solo es cuestión de buscar: microsoft-web-helper</p>
<p>en la herramienta de nuget y proceder a instalarlo</p>
<p><a href="http://lh5.ggpht.com/-CkasKc1viJw/UpHoy5tkSjI/AAAAAAAABjc/S1mlYhMZ380/s1600-h/SNAGHTML988cc7c2%25255B7%25255D.png" target="_blank"><img title="SNAGHTML988cc7c2" style="display: inline" alt="SNAGHTML988cc7c2" src="http://lh5.ggpht.com/-iiy_XbKSV1E/UpHozvd1J-I/AAAAAAAABjk/P-L2zip3Jik/SNAGHTML988cc7c2_thumb%25255B4%25255D.png?imgmax=800" width="592" height="374" /></a></p>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p>Para el ejemplo hemos utilizado Visual Studio 2012, la base de datos se encuentra en la carpeta App_Data del proyecto web</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21433&authkey=AEQOp2MJHNTm8Ws" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com26tag:blogger.com,1999:blog-7361892840793499128.post-27346805144929704702013-10-01T10:22:00.000-07:002013-11-24T09:33:43.081-08:00MVP C# 2013<p> </p> <p>Simplemente quería agradecer por ser reconocido por cuarto año.</p> <p>Esto marca que el camino es el correcto, así que seguiré ayudando y aportando mi granito de arena.</p> <p> </p> <p><a href="http://lh3.ggpht.com/-PBRxrSF6D1I/UpI4cs3i53I/AAAAAAAABj0/V3RSo70CCuU/s1600-h/Leandro%252520Tuttini%252520-%252520mvp2013%25255B7%25255D.jpg" target="_blank"><img title="Leandro Tuttini - mvp2013" style="float: none; margin-left: auto; display: block; margin-right: auto" alt="Leandro Tuttini - mvp2013" src="http://lh4.ggpht.com/-sTr-jMtZ-1k/UpI4dUcwOZI/AAAAAAAABj4/XYWunrGHkH4/Leandro%252520Tuttini%252520-%252520mvp2013_thumb%25255B4%25255D.jpg?imgmax=800" width="563" height="429" /></a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com11tag:blogger.com,1999:blog-7361892840793499128.post-73906231576026608602013-09-09T03:24:00.000-07:002013-10-14T03:31:51.737-07:00[Entity Framework][Code First] Dividir Tabla (Table Splitting)<p> </p> <p><strong>Introducción <hr /></strong></p> <p>Cuando se definen entidades estas pueden contener atributos que no siempre se quiere recuperar, ya que de hacerlo podría afectar la performance. Un ejemplo se observa en aquellos atributos donde se persisten datos de un volumen importante, como ser campos del tipo image, varbinary o ntext, los cuales por lo general no son necesarios recuperar cuando se carga un listado de la entidad en un grid, pero si son necesarios cuando se edita una entidad individual.</p> <p>Por supuesto no solo esta limitado a este uso, se podría dividir simplemente porque un conjunto de propiedades representan la información clave de la entidad la cual es frecuentemente utilizada, separándola de aquellos atributos donde, salvo en algún uso puntual, no siempre se requieren.</p> <p>Esta división permite mapear una tabla simple en múltiples entidades.</p> <p> </p> <h5>Complex Type Vs Table Splitting <hr /></h5> <p>Seguramente se estén preguntando, porque simplemente no se usa un Tipo Complejo? definiendo en este las propiedades que se consideran especiales.</p> <p>El tema pasa porque los Tipo Complejos no permiten la carga retardada (lazy load), mientras que la división de la tabla en varias entidades si lo permite.</p> <p>Al recuperar una entidad los tipo complejo serán siempre devueltos, no hay control sobre esta entidad, mientras que una tabla dividida en entidades se le puede indicar cuando se necesita recuperar este bloque de datos.</p> <p> </p> <p><strong>Definición del modelo <hr /></strong></p> <p>El objetivo es permitir separar un conjunto de propiedades en una entidad distinta</p> <p><a href="http://lh4.ggpht.com/-wEFwcS5djTI/UlvH4g8CWLI/AAAAAAAABa4/qXDFKV6WS1g/s1600-h/image%25255B5%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-Q8UbKqfdovY/UlvH5GNQNzI/AAAAAAAABbA/YO6CloxZ1Po/image_thumb%25255B3%25255D.png?imgmax=800" width="481" height="246" /></a></p> <p>a pesar de esta dividido en varias entidades todas son mapeadas a la misma tabla, por lo que resulta</p> <p><a href="http://lh5.ggpht.com/-Y-Y_pFHb4Mk/UlvH53cCTHI/AAAAAAAABbI/b7Oxy9dOL9M/s1600-h/image%25255B11%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-BHGC51B8IDU/UlvH6Uva9wI/AAAAAAAABbQ/Fh_djvMUsZM/image_thumb%25255B7%25255D.png?imgmax=800" width="423" height="370" /></a></p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:5b3cd54e-ef1f-41b4-84c6-b600f2a4797a" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Employee
{
public Employee()
{
this.Localization = new Localization();
}
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual Localization Localization { get; set; }
public virtual EmployeeExtended EmployeeExt { get; set; }
}
public class EmployeeExtended
{
public int EmployeeID { get; set; }
public byte[] Photo { get; set; }
public string PhotoPath { get; set; }
public string Notes { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La entidad principal define una propiedad que referencia a la entidad extendida, a la vez que ambas entidades definen la misma propiedad como key, ya que por medio de esta se relacionan.</p>
<p> </p>
<p><strong>Definición del Mapping </strong></p>
<hr />
<p>Al definir el mapping ambas entidades persisten en la misma tabla, por eso el uso del ToTable(). También hacen uso de la misma key. </p>
<p>El HasRequired() permite indicar cual será la propiedad que representaran los datos extendidos en la entidad principal.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:3867e8dc-eda0-492b-b66b-744f4c89a9af" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
ToTable("Employees");
HasKey(x => x.EmployeeID);
Property(x => x.LastName)
.HasMaxLength(20)
.IsRequired();
Property(x => x.FirstName)
.HasMaxLength(10)
.IsRequired();
HasRequired(x => x.EmployeeExt)
.WithRequiredPrincipal();
}
}
public class EmployeeExtendedMap : EntityTypeConfiguration<EmployeeExtended>
{
public EmployeeExtendedMap()
{
ToTable("Employees");
HasKey(x => x.EmployeeID);
Property(x => x.Notes)
.HasColumnType("ntext")
.IsOptional();
Property(x => x.Photo)
.HasColumnType("image")
.IsOptional();
Property(x => x.PhotoPath)
.HasColumnType("nvarchar")
.HasMaxLength(255)
.IsOptional();
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong></strong></p>
<p><strong>Definición del Repositorio </strong>
<hr /></p>
<p>Hay cierta funcionalidad que requiere un tratamiento especial para estos casos es que se crea funcionalidad en el repositorio de le entidad empleado.</p>
<p>Se define esta en la interfaz, para luego implementarla en la clase concreta.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:9f19fcb7-ac39-4460-ad0b-a80b39d28bd3" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public interface IEmployeeRepository : IRepository<Employee>
{
EmployeeExtended GetExtendedById(int id);
void DeleteIncludeExtended(Employee entity);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a4182ce5-9b60-475a-b5cf-69e4d2ed2c98" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
/// <summary>
/// Recupera solo la entidad Extendida
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public EmployeeExtended GetExtendedById(int id)
{
using (NorthWindContext context = new NorthWindContext())
{
return context.EmployeeExtendeds.FirstOrDefault(x => x.EmployeeID == id);
}
}
/// <summary>
/// Elimina la entidad incluyendo la informacion extendida
/// en caso de tenerla
/// </summary>
/// <param name="entity"></param>
public void DeleteIncludeExtended(Employee entity)
{
using (NorthWindContext context = new NorthWindContext())
{
if (entity.EmployeeExt == null)
entity.EmployeeExt = new EmployeeExtended() { EmployeeID = entity.EmployeeID };
context.Employees.Attach(entity);
context.Employees.Remove(entity);
context.SaveChanges();
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Estos método serán utilizados en los Test, donde se probara su funcionalidad</p>
<p> </p>
<p><strong>Configuración del proyecto de Test
<hr /></strong></p>
<p>Para probar la funcionalidad se requiere que un archivo que representa la foto que será asignada a la propiedad de la entidad. Este archivo forma parte del proyecto de Test, pero debe ser copiado a la carpeta de output.</p>
<p>Sera necesario definir un archivo de TestSetting el cual no ayudara a configurar el entorno de prueba, para ello agregamos este archivo usando la opción </p>
<p><a href="http://lh5.ggpht.com/-qW9BVbKNsfw/UlvH7CVlkDI/AAAAAAAABbY/9rW2gExkrKs/s1600-h/image%25255B23%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-rRV3kz9XxMA/UlvH7uayyfI/AAAAAAAABbg/3IoKuE2W5vc/image_thumb%25255B15%25255D.png?imgmax=800" width="440" height="296" /></a></p>
<p> </p>
<p><a href="http://lh6.ggpht.com/-E4UwOYlfpR0/UlvH8BSq56I/AAAAAAAABbo/W7s6yMQJZwc/s1600-h/SNAGHTML4800a637%25255B6%25255D.png" target="_blank"><img title="SNAGHTML4800a637" style="display: inline" alt="SNAGHTML4800a637" src="http://lh3.ggpht.com/-0_JcGvyTlqg/UlvH8ppZYjI/AAAAAAAABbw/00dUzhordZ8/SNAGHTML4800a637_thumb%25255B3%25255D.png?imgmax=800" width="448" height="258" /></a></p>
<p>En este configuramos las opciones de deploy para que copie el archivo de la imagen a la carpeta que defina el test cuando ejecute</p>
<p><a href="http://lh4.ggpht.com/-OVI7VBdOkCE/UlvH9LXIz3I/AAAAAAAABb4/y4dmwZsErfE/s1600-h/SNAGHTML480139af%25255B6%25255D.png" target="_blank"><img title="SNAGHTML480139af" style="display: inline" alt="SNAGHTML480139af" src="http://lh3.ggpht.com/-jolrVFKbjA0/UlvH90UAjXI/AAAAAAAABcA/SLA1VbnRosI/SNAGHTML480139af_thumb%25255B3%25255D.png?imgmax=800" width="451" height="336" /></a></p>
<p>Asignamos cual será el archivo usado en la ejecución de los test </p>
<p><a href="http://lh6.ggpht.com/-JFl304osiXg/UlvH-VsJp6I/AAAAAAAABcI/JYsLCdbwPb8/s1600-h/image%25255B17%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-CSklhI3MLpM/UlvH_PrWfOI/AAAAAAAABcQ/Q3CAxD2jTto/image_thumb%25255B11%25255D.png?imgmax=800" width="449" height="176" /></a></p>
<h5> </h5>
<p><a href="http://lh6.ggpht.com/-3Pe0SWLO-wk/UlvH_r_MFFI/AAAAAAAABcY/kKa4MNMfrIQ/s1600-h/SNAGHTML47f6aaaa%25255B7%25255D.png" target="_blank"><img title="SNAGHTML47f6aaaa" style="display: inline" alt="SNAGHTML47f6aaaa" src="http://lh4.ggpht.com/-TKT8smJ3Y48/UlvIATP3nbI/AAAAAAAABcg/sDlXte3Kcnk/SNAGHTML47f6aaaa_thumb%25255B4%25255D.png?imgmax=800" width="690" height="316" /></a></p>
<p> </p>
<p><strong>Test – Eliminar empleado con y sin información extendida
<hr /></strong></p>
<p>Si eliminamos una entidad haciendo uso del método Delete() del repositorio base obtendremos un error</p>
<p><a href="http://lh4.ggpht.com/-HSlgDtMvc8Y/UlvIBfmDoYI/AAAAAAAABco/0mscGl2UBu8/s1600-h/image%25255B36%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-7e1q6fYy8_o/UlvIBzaDIxI/AAAAAAAABcw/3HPYNV2uXnM/image_thumb%25255B24%25255D.png?imgmax=800" width="598" height="304" /></a></p>
<p>Se produce porque se requiere no solo marcar para eliminar la entidad principal sino también la extendida, por esta razón en el repositorio de la entidad empleado se creo un método especifico para este caso.</p>
<p>El método DeleteIncludeExtended() elimina la entidad y también define la entidad extendida a pesar que no se envíe como dato en la entidad que se asigna como parámetro.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:f613abf0-4c66-4c98-b3e2-c1a016852817" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Delete_Employee()
{
EmployeeRepository repoEmployee = new EmployeeRepository();
//
//se crea un empleado con info extendida
//
CreateEmployeeWithPhoto();
//elimina el empleado y su informacion extendida
repoEmployee.DeleteIncludeExtended(new Employee() { EmployeeID = employeeNew.EmployeeID } );
//se recupera el empleado que fue eliminado en el paso anterior
Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);
Assert.IsNull(employeeSel);
//
//se crean el empleados sin informacion extendida
//
Employee employee = new Employee()
{
FirstName = "name 1",
LastName = "lastname 1",
EmployeeExt = new EmployeeExtended()
};
repoEmployee.Create(employee);
repoEmployee.DeleteIncludeExtended(employee);
//se recupera el empleado que fue eliminado en el paso anterior
employeeSel = repoEmployee.Single(x => x.EmployeeID == employee.EmployeeID);
Assert.IsNull(employeeSel);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El test tiene dos partes, la primera crea un empleado con funcionalidad extendida y lo elimina usando el método que hemos creado en el repositorio para este propósito, la segunda parte crea una entidad sin información extendida y también elimina esta entidad, demostrando que sin importar la información con la cual se cree la entidad puede ser eliminada.</p>
<p>Como se vera para ambos casos es necesario definir el id de la entidad extendida para que sea marcada tanto la entidad principal como la extendida para ser eliminadas.</p>
<p> </p>
<p><strong>Test – Obtener todos los empleados
<hr /></strong></p>
<p>Se crean dos empleado, uno con información extendida y el otro sin ella.</p>
<p>En el test recupera la lista de empleados, pudiéndose analizarse como la información extendida no se obtiene, mientas que la propiedad que representa el complex type si es devuelta al recuperar la entidad.</p>
<p>Este caso es el típico que uno usaría para cargar un listado o grid con la información base de la entidad. </p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:0067af29-b33a-4164-b328-3d82dc93d5d6" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAll_Employee()
{
string foto = Path.Combine(this.TestContext.DeploymentDirectory, "foto.jpg");
EmployeeRepository repoEmployee = new EmployeeRepository();
//se eliminan las entidades que pudieran quedar de la ejecucion de test anteriores
repoEmployee.GetAll().ForEach(x => repoEmployee.DeleteIncludeExtended(x));
//se crean dos empleados
CreateEmployeeWithPhoto();
Employee employee2 = new Employee()
{
FirstName = "name 2",
LastName = "lastname 2",
EmployeeExt = new EmployeeExtended()
};
repoEmployee.Create(employee2);
List<Employee> list = repoEmployee.GetAll();
Assert.AreEqual(list.Count, 2);
Assert.IsNull(list[0].EmployeeExt);
Assert.IsNotNull(list[0].Localization);
Assert.IsTrue(list[0].Localization.HasValue);
Assert.IsNull(list[1].EmployeeExt);
Assert.IsNotNull(list[1].Localization);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong></strong></p>
<p><strong>Test – Obtener entidad SIN información extendida
<hr /></strong></p>
<p>Aunque se haya creado un empleado con información adicional al recuperar la entidad esta no forma parte de los datos.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d1b4b42a-bd6b-46f1-9bd9-6b938dbb0637" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetById_Employee()
{
//se crea el empleado con informacion extendida
CreateEmployeeWithPhoto();
//Se recupera la entidad
Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);
Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
Assert.IsTrue(employeeSel.Localization.HasValue);
Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
Assert.AreEqual(employeeSel.Localization.City, "City 1");
Assert.AreEqual(employeeSel.Localization.Country, "Country 1");
Assert.IsNull(employeeSel.EmployeeExt);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La consulta recupera el empleado y deja evidencia de la inclusión de los campos que define el complex type pero no se incluyen los de la entidad extendida.</p>
<p><a href="http://lh4.ggpht.com/-KsMWF7zrniI/UlvICTHXdXI/AAAAAAAABc4/_6SPMjwgr48/s1600-h/image%25255B42%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-hICzUd9wlKM/UlvIDIl6ZDI/AAAAAAAABdA/50KkWoGkj90/image_thumb%25255B28%25255D.png?imgmax=800" width="635" height="182" /></a></p>
<p> </p>
<p><strong>Test – Obtener entidad CON información extendida
<hr /></strong></p>
<p>En este caso el test define un “include” de la propiedad extendida, permitiendo recuperar la foto asignada al empleado</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:aede0364-dbf7-4ede-90c3-df7bc173b822" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetById_EmployeeExt()
{
//se crea el empleado con informacion extendida
CreateEmployeeWithPhoto();
//se recupera la entidad y sus propiedad extendida
Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID,
new List<Expression<Func<Employee, object>>>()
{
x=>x.EmployeeExt
});
Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
Assert.IsTrue(employeeSel.Localization.HasValue);
Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
Assert.AreEqual(employeeSel.Localization.City, "City 1");
Assert.AreEqual(employeeSel.Localization.Country, "Country 1");
Assert.IsNotNull(employeeSel.EmployeeExt);
Assert.IsNotNull(employeeSel.EmployeeExt.Photo);
Assert.AreEqual(employeeSel.EmployeeExt.PhotoPath, employeeNew.EmployeeExt.PhotoPath);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>la consulta resultante es bastante mas compleja ya que debe unir los campos de la entidad extendida</p>
<p><a href="http://lh3.ggpht.com/-qeLGX8Yupio/UlvIDpXpCRI/AAAAAAAABdI/cIKn0hl2rmQ/s1600-h/image%25255B48%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-WJyc7SBpy-U/UlvIEIAHmUI/AAAAAAAABdQ/UxRV5ShY2jw/image_thumb%25255B32%25255D.png?imgmax=800" width="751" height="164" /></a></p>
<p>Lo importante a destacar es que uno controla cuando es necesario recuperar los datos extendido para así mejorar la performance de la aplicación.</p>
<p> </p>
<p><strong>Test – Recuperar SOLO la información extendida
<hr /></strong></p>
<p>Si ya se dispone de la entidad con los datos básicos y se requiere solo los datos extendidos se podría recuperar únicamente estos.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:703bfb60-a57f-4ba1-bec4-c92a3b6484ac" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetById_Employee_RecoverOnlyExtended()
{
CreateEmployeeWithPhoto();
EmployeeExtended employeeSel = repoEmployee.GetExtendedById(employeeNew.EmployeeID);
Assert.IsNotNull(employeeSel);
Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
Assert.IsNotNull(employeeSel.Photo);
Assert.AreEqual(employeeSel.PhotoPath, employeeNew.EmployeeExt.PhotoPath);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El método GetExtendedById() hace uso de la propiedad del contexto que accede a esta entidad extendida de forma directa.</p>
<p><a href="http://lh6.ggpht.com/-IxJBNNfMnbE/UlvIEp4cLTI/AAAAAAAABdY/WT7qZPpLh48/s1600-h/image%25255B54%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-D9FLgs02mEE/UlvIFRbGsEI/AAAAAAAABdg/ph6VOVNP8aE/image_thumb%25255B36%25255D.png?imgmax=800" width="643" height="158" /></a></p>
<p>Es la misma consulta que recupera al empleado pero solo define los campos de la entidad extendida.</p>
<p> </p>
<p><strong>Documentación de referencia </strong></p>
<strong>
<hr /></strong>
<p> <a href="http://weblogs.asp.net/manavi/archive/2011/04/24/associations-in-ef-4-1-code-first-part-4-table-splitting.aspx" target="_blank">Associations in EF Code First: Part 4 – Table Splitting</a> </p>
<p> </p>
<p><strong>Código </strong></p>
<strong>
<hr /></strong>
<p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF</p>
<p> </p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21422&authkey=ALjG9tfHIOfnl3Q" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com28tag:blogger.com,1999:blog-7361892840793499128.post-11614753960519901232013-08-30T06:43:00.000-07:002013-10-06T14:09:56.617-07:00[Entity Framework][Code First] Herencia - Tabla por tipo concreto - Table per Concrete Type (TPC)<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>Se continua analizando las diferentes implementaciones de herencia que Entity Framework nos permite modelar. </p> <p>En este caso cada tipo mapeara a una tabla la cual define todos los campos, ya sean los particulares que este defina, así como los declarados en la clase base.</p> <p>Este artículo utilizara el mismo modelo de los ejemplos anteriores:</p> <p><a href="http://ltuttini.blogspot.com.ar/2013/08/entity-frameworkcode-first-herencia_28.html" target="_blank">[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)</a></p> <p><a href="http://ltuttini.blogspot.com.ar/2013/08/entity-frameworkcode-first-herencia.html" target="_blank">[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)</a></p> <p> </p> <p><strong>Definición del modelo </strong></p> <hr /> <p>Partiremos de un modelo de clases ya conocido en los artículos anteriores.</p> <p><a href="http://lh6.ggpht.com/-kKMrwqVirPY/UlHReWTP5nI/AAAAAAAABYg/2gNumVS44Tg/s1600-h/image%25255B5%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-2kdIbnqShMs/UlHRe1hfT2I/AAAAAAAABYo/uhYGm73z4RM/image_thumb%25255B3%25255D.png?imgmax=800" width="394" height="407" /></a></p> <p>y obtendremos las tablas</p> <p><a href="http://lh6.ggpht.com/-xKluwpJvMCg/UlHRffFrp1I/AAAAAAAABYw/WbcOVWk4J2w/s1600-h/image%25255B12%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-wTsgGkbWGWc/UlHRf5K9G8I/AAAAAAAABY4/f8FP2N8XULA/image_thumb%25255B8%25255D.png?imgmax=800" width="696" height="291" /></a></p> <p>La definición de cada clase derivada creara una tabla para si misma, llevando no solo las propiedades que esta defina como columnas sino que también lo hará con las de la clase base. </p> <p>Un punto que se debe remarcar es que no hay relación entre las tablas.</p> <p>En este ejemplo la clase Employee se define como “abstract” lo cual impide crear instancias de esta, es por eso que solo se crean dos tablas, si la base no fuera “abstract” se crearía una tercer tabla para soportar los datos de la entidad base.</p> <p> </p> <p><strong>Definición del Mapping <hr /></strong></p> <p>Para cada entidad derivada se define la clase de mapping donde se especifica el nombre de la tabla.</p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:56df6f6c-1e00-4061-bb01-72aa8f20efda" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class EmployeeInternalMap : EntityTypeConfiguration<EmployeeInternal>
{
public EmployeeInternalMap()
{
Map(x =>
{
x.MapInheritedProperties();
x.ToTable("InternalEmployee");
});
}
}
public class EmployeeExternalMap : EntityTypeConfiguration<EmployeeExternal>
{
public EmployeeExternalMap()
{
Map(x =>
{
x.MapInheritedProperties();
x.ToTable("ExternalEmployee");
});
Property(x => x.ConsultantName).IsRequired()
.HasColumnType("varchar")
.HasMaxLength(100);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Se destaca del mapping la invocación al método <a href="http://msdn.microsoft.com/en-us/library/gg679174%28v=vs.103%29.aspx" target="_blank">MapInheritedProperties</a>(), lo cual aplica un re-mapping las propiedades de la clase base.</p>
<p><a href="http://lh6.ggpht.com/-FwnGSAoYxS0/UlHRgUorDEI/AAAAAAAABZA/ewxwByJz7GY/s1600-h/image%25255B19%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-wK_xAOcxZR4/UlHRg7RpEOI/AAAAAAAABZI/Zq3OgBHRE5M/image_thumb%25255B13%25255D.png?imgmax=800" width="520" height="456" /></a></p>
<p> </p>
<p><strong>Problema con la key de la tabla
<hr /></strong></p>
<p>Si los test los diseñamos en base a los artículos anteriores, al ejecutarlos obtendremos un error con la clave primaria.</p>
<p><a href="http://lh4.ggpht.com/-Ig8hEoisWZs/UlHRhSuN5HI/AAAAAAAABZQ/azr-0qHCEus/s1600-h/SNAGHTML23be7cf7%25255B7%25255D.png" target="_blank"><img title="SNAGHTML23be7cf7" style="display: inline" alt="SNAGHTML23be7cf7" src="http://lh5.ggpht.com/-WM7rX5UQS8s/UlHRiFXnetI/AAAAAAAABZY/1SvlNFWUZS0/SNAGHTML23be7cf7_thumb%25255B4%25255D.png?imgmax=800" width="688" height="324" /></a></p>
<p>La causa se debe a que este forma de mapear la herencia no define columnas identity para las claves, por medio de código se tendrá que asegurar el id secuencial que se asigna a la entidad cuando esta se persiste.</p>
<p>Para solucionar este problema se definió en la repositorio un método que nos ayudara a obtener el ultimo id. El método GetLastId() recupera el ultimo utilizado en la combinación de las tablas que definen la herencia.</p>
<p>Se lo declara primeramente en la interfaz.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:b054f402-6d57-4596-bece-0e717dd292af" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public interface IEmployeeRepository : IRepository<Employee>
{
List<EmployeeInternal> GetAllInternalType();
List<Employee> GetAllExternalType();
int GetLastId();
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Y luego en la clase que implementa el repositorio.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:cd9c7d83-6267-416b-8e86-6c921c5bf02d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "> public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
public int GetLastId()
{
using (NorthWindContext context = new NorthWindContext())
{
int? lastId = context.Employees.Max(x => (int?)x.EmployeeID);
return lastId.HasValue ? lastId.Value : 0;
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>La ejecución del mismo implica un UNION ALL entre las tablas que deriven de la misma base para luego aplicar el MAX() del campo definido como key</p>
<p><a href="http://lh3.ggpht.com/-LgLyCyXGgog/UlHRiokv6mI/AAAAAAAABZg/JMl2OWRc47E/s1600-h/image%25255B25%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-c7sCfvwkMPM/UlHRjfd2IMI/AAAAAAAABZo/ddFPTUhcicE/image_thumb%25255B17%25255D.png?imgmax=800" width="623" height="186" /></a> </p>
<p> </p>
<p><strong>Test – Inicializar datos
<hr /></strong></p>
<p>La inicialización requiere que por cada entidad creada se realice una consulta previa para recuperar el ultimo id</p>
<p><a href="http://lh5.ggpht.com/-wHfBZxRVfUY/UlHRkh0de9I/AAAAAAAABZw/Tcsq61h_zuY/s1600-h/image%25255B31%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-tixjO9q6XeU/UlHRlXbyLGI/AAAAAAAABZ4/SaiP0psRWg8/image_thumb%25255B21%25255D.png?imgmax=800" width="734" height="331" /></a></p>
<p>En la imagen se observa como a pesar de ser diferentes tablas se mantiene el consecutivo en la key.</p>
<p> </p>
<p><strong>Test – Obtener todos los empleados
<hr /></strong></p>
<p>La query generada para recuperar todos los empleados requiere unir las tablas de cada clase derivada.</p>
<p><a href="http://lh4.ggpht.com/--VaEFEG9qRw/UlHRl4AL-LI/AAAAAAAABaA/1PddaJeiq6I/s1600-h/image%25255B37%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-f6_0bbGjahA/UlHRmlKgJVI/AAAAAAAABaI/donfG2RtKZY/image_thumb%25255B25%25255D.png?imgmax=800" width="702" height="379" /></a></p>
<p> </p>
<p><strong>Test – Recuperar todos los empleados del tipo interno
<hr /></strong></p>
<p>Ya sea usando un repositorio especial para el tipo concreto “<em>GetAllInternal_UsingSpecificRepository_Employee()</em>” o definiendo un método en el repositorio base el cual podria utilizar el OfType<>  “<em>GetAllInternal_UsingGenericRepository_Employee()</em>”, el resultado es idéntico, solo realiza la query sobre una tabla en particular donde se define el tipo. </p>
<p> </p>
<p><a href="http://lh6.ggpht.com/-ZNV_Fq6B0mo/UlHRnORI_ZI/AAAAAAAABaM/TJr2AwpioN8/s1600-h/image%25255B43%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-RkG6x90ZJgs/UlHRnjXVsgI/AAAAAAAABaY/7F52sb0EDnc/image_thumb%25255B29%25255D.png?imgmax=800" width="617" height="206" /></a></p>
<p> </p>
<p><strong>Test – Obtener todos los empleados Externos</strong><strong>
<hr /></strong></p>
<p>Al igual que los test anteriores, hacer uso de un repositorios especifico “<em>GetAllExternal_Employee()</em>” para recuperar los empleados externos o definir un método en el repositorio base que haga uso del “is” para definir que tipo de entidad cargar “<em>GetAllExternal_UsandoIs_Employee()</em>”, resulta en la mismo SELECT.</p>
<p> </p>
<p><a href="http://lh6.ggpht.com/-MBArJee9Fwo/UlHRoAhUmTI/AAAAAAAABag/TuMJnS37Reo/s1600-h/image%25255B49%25255D.png" target="_blank"><strong><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-Ili4sYVWC5s/UlHRor0oWSI/AAAAAAAABao/bhJdOqZ1T1M/image_thumb%25255B33%25255D.png?imgmax=800" width="573" height="243" /></strong></a></p>
<p> </p>
<p><strong>Resumen
<hr /></strong></p>
<p>Si bien en algunos casos este tipo de persistencia puede resultar útil, el hecho de no poder definir un campo clave como secuencial puede dificultar su implementación.</p>
<p>La creación de diferentes tablas generan redundancia de campos al duplicarlos, lo cual no lo hace un modelo de persistencia aconsejable, aunque puede que esta sea una característica buscada cuando no se requiere un modelo normalizado.</p>
<p>En resumen, analizar previamente los modelos de herencia TPT y TPH, dejando como ultima alternativa el TPC.</p>
<p>El modelo TPH hace uso de un discriminador para definir los tipos concretos. El TPT, lo realiza mediante la relación entre las tablas. En TPC en cambio la tabla completa define el tipo.</p>
<p> </p>
<p><strong>Documentación de referencia </strong></p>
<hr />
<p><a href="http://weblogs.asp.net/manavi/archive/2011/01/03/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-3-table-per-concrete-type-tpc-and-choosing-strategy-guidelines.aspx" target="_blank">Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)</a> </p>
<p> </p>
<p><strong>Código </strong></p>
<hr />
<p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21417&authkey=ANYJc6yQbIW-eQg" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com0tag:blogger.com,1999:blog-7361892840793499128.post-35329695620033155782013-08-28T15:24:00.000-07:002013-09-15T06:40:57.224-07:00[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>Analizaremos un modelo de herencia el cual permite definir una tabla para cada tipo concreto. En este caso la relación entre las tablas define el tipo de instancia de la entidad.</p> <p>El modelo utilizado en este artículo es idéntico al anterior:</p> <p><a href="http://ltuttini.blogspot.com.ar/2013/08/entity-frameworkcode-first-herencia.html" target="_blank">[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)</a> </p> <p> </p> <p><strong>Definición del modelo <hr /></strong></p> <p>Modelaremos la entidad empleado y sus derivados que representan al personal externo y el contratado por la compañía.</p> <p>Partiremos de un modelo de clases como el siguiente:</p> <p><a href="http://lh3.ggpht.com/-7hlbpcE-ZR0/UjW4EEYp28I/AAAAAAAABVA/UbSppyDzn38/s1600-h/image5.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-mCT4t3oIjAM/UjW4Eg9Ph6I/AAAAAAAABVI/_ZO1Vgm7Scc/image_thumb3.png?imgmax=800" width="380" height="393" /></a></p> <p>y obtendremos un modelo de tablas </p> <p><a href="http://lh5.ggpht.com/-kdU0uP7cWI8/UjW4FH47JHI/AAAAAAAABVQ/_QPBNcyXvwY/s1600-h/image11.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-JGoSyMRddgo/UjW4FtLdmtI/AAAAAAAABVY/SqMqmU4Iq18/image_thumb7.png?imgmax=800" width="582" height="347" /></a></p> <p>en donde cada la clase base estará en una tabla mientras que los derivados, con sus atributos particulares, en otras distintas. </p> <p>En este caso la relación entre las tablas define el tipo, a diferencia del modelo TPH (Tabla por jerarquía) en donde un campo era quien lo indicaba.</p> <p> </p> <p><strong>Definición del Mapping <hr /></strong></p> <p>Se va a necesitar definir en que tabla se persiste cada tipo especifico, es por ello que se requiere una clase de mapping para cada derivado que herede de la base.</p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:338ac403-2104-450b-8810-9a213f738b5e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new EmployeeMap());
modelBuilder.Configurations.Add(new EmployeeInternalMap());
modelBuilder.Configurations.Add(new EmployeeExternalMap());
base.OnModelCreating(modelBuilder);
}
}
public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
HasKey(x => x.EmployeeID);
Property(x => x.LastName).HasMaxLength(20).IsRequired();
Property(x => x.FirstName).HasMaxLength(10).IsRequired();
Property(x => x.Address).HasMaxLength(60);
Property(x => x.City).HasMaxLength(15);
Property(x => x.Region).HasMaxLength(15);
Property(x => x.PostalCode).HasMaxLength(10);
Property(x => x.Country).HasMaxLength(15);
}
}
public class EmployeeInternalMap : EntityTypeConfiguration<EmployeeInternal>
{
public EmployeeInternalMap()
{
ToTable("EmployeeInternal");
}
}
public class EmployeeExternalMap : EntityTypeConfiguration<EmployeeExternal>
{
public EmployeeExternalMap()
{
ToTable("EmployeeExternal");
Property(x => x.ConsultantName).IsRequired()
.HasColumnType("varchar")
.HasMaxLength(100);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El principal detalle esta en la definición de los nombre de la tablas</p>
<p><a href="http://lh3.ggpht.com/-bru4PZrDfuA/UjW4GFxVAeI/AAAAAAAABVg/wrQLnfQHh6M/s1600-h/image%25255B7%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-BIRTa-2L-mY/UjW4GmoQRpI/AAAAAAAABVo/6cvDcQS2nAs/image_thumb%25255B4%25255D.png?imgmax=800" width="512" height="330" /></a></p>
<p> </p>
<p><strong>Definición Repository
<hr /></strong></p>
<p>Este no sufrió cambios con respecto al articulo anterior donde tratamos TPH.</p>
<p>Nota: se agrego un método adicional a la interfaz del repositorio el cual implementa una query linq que utiliza el “is”, pero no varia la forma de definir esta funcionalidad con respecto al articulo anterior. Se analizara este punto más adelante cuando tratemos el Test que utiliza esta implementacion.</p>
<p> </p>
<p><strong>Test – Inicializar datos </strong></p>
<hr />
<p>La inicialización de datos es idéntica al artículo anterior donde explique TPH.</p>
<p>Pero el resultado de los “insert” que define EF son bastante diferentes, ya que por cada entidad creada requiere realiza dos operaciones, uno en la tabla base y otro en cada especialización, esto se aprecia claramente en el trace tomado desde el sql profiler.</p>
<p><a href="http://lh5.ggpht.com/-J4g6Rcd7ioo/UjW4HIbnLXI/AAAAAAAABVw/-a-1rJIX0hc/s1600-h/image18.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-3Efk8nbE_3I/UjW4HwtacLI/AAAAAAAABV4/Nl1gKpIVA0c/image_thumb12.png?imgmax=800" width="705" height="411" /></a></p>
<p> </p>
<p><strong>Test – Obtener todos los empleados
<hr /></strong></p>
<p>Recuperar todos los empleados requiere implementar la unión de dos tablas, la de empleados internos y externos,  al ejecutar el test se puede observar claramente si analizamos el select definido por EF</p>
<p><a href="http://lh5.ggpht.com/-QU8Mm5jD0Ug/UjW4If0KUTI/AAAAAAAABWA/1zAOz0CXdJY/s1600-h/image%25255B14%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-3ATb3_Sts54/UjW4JCJIbcI/AAAAAAAABWI/uY2V7lQAAf4/image_thumb%25255B9%25255D.png?imgmax=800" width="746" height="386" /></a></p>
<p>La unión de las dos tablas que representan las entidades hijas se unen mediante un INNER JOIN con la tabla padre de empleados</p>
<p>Aquí nuevamente aparecen los “0X0X” y “0X1X” los cuales son usados por EF para determinar que tipo de instancia debe crear</p>
<p> </p>
<p><strong>Test – Recuperar todos los empleados de tipo interno, usando un repositorio especifico
<hr /></strong></p>
<p>Utilizamos el repositorio definido para un tipo concreto, en este caso se recuperan los empleados internos a la empresa </p>
<p><a href="http://lh6.ggpht.com/-zVrBS-d90kc/UjW4Jj7G-0I/AAAAAAAABWQ/SlX8_M3yJVE/s1600-h/SNAGHTML31ecfb93%25255B7%25255D.png" target="_blank"><img title="SNAGHTML31ecfb93" style="display: inline" alt="SNAGHTML31ecfb93" src="http://lh4.ggpht.com/-MiiTbgDdc3Q/UjW4KdK1w3I/AAAAAAAABWY/ZDFA4M-QWWQ/SNAGHTML31ecfb93_thumb%25255B4%25255D.png?imgmax=800" width="571" height="240" /></a></p>
<p>Esta acción unirá mediante un INNER JOIN la tabla que representa estos empleados con la tabla base, del cruce se obtendrán solo los registros de un único tipo.</p>
<p> </p>
<p><a href="http://lh4.ggpht.com/-npD1lqttQXk/UjW4KoFUQ3I/AAAAAAAABWg/Kcgqqc-xns0/s1600-h/image%25255B21%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-JEC6aw_IStE/UjW4Ldx23wI/AAAAAAAABWo/ym4H5IzBUVU/image_thumb%25255B14%25255D.png?imgmax=800" width="760" height="239" /></a></p>
<p> </p>
<p><strong>Test – Recuperar todos los empleados del tipo interno, usando funcionalidad del repositorio genérico
<hr /></strong></p>
<p>Utilizar el método OfType<> definido en un método en al repositorio general </p>
<p><a href="http://lh6.ggpht.com/-vLqVpEkSpyY/UjW4L8FBDjI/AAAAAAAABWw/xs-XuQYNlvo/s1600-h/SNAGHTML31f473ce%25255B6%25255D.png" target="_blank"><img title="SNAGHTML31f473ce" style="display: inline" alt="SNAGHTML31f473ce" src="http://lh3.ggpht.com/-BNHFWVur5CU/UjW4MTTgQsI/AAAAAAAABW4/brIEkvb8mlg/SNAGHTML31f473ce_thumb%25255B3%25255D.png?imgmax=800" width="724" height="196" /></a></p>
<p>dará el mismo resultado que utilizar el repositorio especifico del test anterior</p>
<p><a href="http://lh6.ggpht.com/-kRPIzySUSNQ/UjW4NFgkeUI/AAAAAAAABXA/fJqA_3B5adE/s1600-h/image%25255B27%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-TjN-p0n_8l8/UjW4N8vMZkI/AAAAAAAABXI/Ol8bxwBw7ls/image_thumb%25255B18%25255D.png?imgmax=800" width="724" height="222" /></a></p>
<p> </p>
<p><strong>Test – Obtener todos los empleados Externos
<hr /></strong></p>
<p>Se obtienes todos los empleados de contratación externa.</p>
<p><a href="http://lh4.ggpht.com/-IEDETahLx5k/UjW4OPk6t_I/AAAAAAAABXQ/2nNlCepqiMI/s1600-h/SNAGHTML32077f62%25255B6%25255D.png" target="_blank"><img title="SNAGHTML32077f62" style="display: inline" alt="SNAGHTML32077f62" src="http://lh3.ggpht.com/-KMzN9Unq5JQ/UjW4OzZy-EI/AAAAAAAABXY/ycU6W_qq4LU/SNAGHTML32077f62_thumb%25255B3%25255D.png?imgmax=800" width="514" height="213" /></a></p>
<p>El join se realiza utilizando la tabla que representa los empleados externos a la compañía. </p>
<p><a href="http://lh4.ggpht.com/-H6R5rHYlcNo/UjW4PP-uVfI/AAAAAAAABXg/GVzFsmLWquk/s1600-h/image%25255B33%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-37Iv9oIHoOo/UjW4PgXLBFI/AAAAAAAABXo/ahGBD9-GyUk/image_thumb%25255B22%25255D.png?imgmax=800" width="644" height="213" /></a></p>
<p> </p>
<p><strong>Test – Obtener todo los empleados externos, usando el “is”
<hr /></strong></p>
<p>Aplicaremos una variación en la técnica utilizada al definir el tipo que queremos filtrar, para ello nos ayudaremos con el “is” en el where de la query linq.</p>
<p><a href="http://lh5.ggpht.com/-zN7-RhGWYRs/UjW4QMavUFI/AAAAAAAABXw/mJ3-NDA7iRc/s1600-h/SNAGHTML321b1bf0%25255B6%25255D.png" target="_blank"><img title="SNAGHTML321b1bf0" style="display: inline" alt="SNAGHTML321b1bf0" src="http://lh6.ggpht.com/-MChxDs70OAc/UjW4Q9fpLfI/AAAAAAAABX4/9xpL6lVnmdM/SNAGHTML321b1bf0_thumb%25255B3%25255D.png?imgmax=800" width="723" height="245" /></a></p>
<p>como veremos EF detecta el “is” como la acción de recuperar un tipo especifico, por eso el sql que define nuevamente hace uso del JOIN entre las tablas</p>
<p><a href="http://lh3.ggpht.com/-8aXRrgCzUxg/UjW4RWSMzHI/AAAAAAAABYA/U8cfI53iqTA/s1600-h/image%25255B39%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-0TAGZVgiGRg/UjW4R_ffZsI/AAAAAAAABYI/sUXdCB2sO8Q/image_thumb%25255B26%25255D.png?imgmax=800" width="696" height="208" /></a></p>
<p> </p>
<p><strong>Documentación de referencia </strong></p>
<hr />
<a href="http://weblogs.asp.net/manavi/archive/2010/12/28/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt.aspx" target="_blank">Inheritance with EF Code First: Part 2 – Table per Type (TPT)</a>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21411&authkey=AEncQIJx3yi3PqI" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com29tag:blogger.com,1999:blog-7361892840793499128.post-64241906878072902952013-08-26T14:03:00.000-07:002013-09-08T15:21:32.333-07:00[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Una de las principales ventajas al implementar un ORM, en nuestro caso de la mano de Entity Framework, apunta a tener a nuestra disposición todo el poder de la Programación Orientada a Objeto (POO) para modelar nuestras entidades de negocio.</p> <p>La Herencia es una de las practicas mas utilizadas para modelar entidades, pudiendo representar en un modo realista el diseño del negocio, mejor aun si le unimos un fácil mapeo de las entidades con la estructura de la base de datos.</p> <p>Existen tres formas de mapear una estructura de Herencia con tablas:</p> <ol> <li>Tabla por jerarquía - Table per Hierarchy (TPH)</li> <li>Tabla por tipo - Table per Type (TPT) </li> <li>Tabla por tipo concreto - Table per Concrete Type (TPC) </li> </ol> <p> </p> <p>En este articulo tratare el primero de ellos, <strong>Tabla por jerarquía</strong>, en este las diferentes clases que definen la herencia mapean contra una única tabla en la base de datos utilizando un campo discriminador para determinar el tipo especifico.</p> <p> </p> <p><strong>Definición del modelo <hr /></strong></p> <p>En el ejemplo definiremos una entidad Empleado pudiendo encontrarse dos tipo: los empleados internos de la empresa y los de contratación externa. </p> <p> </p> <p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:eff56862-2273-4665-80e3-8423a6786b81" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public abstract class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public class EmployeeExternal : Employee
{
public string ConsultantName { get; set; }
public DateTime? ContactExpiration { get; set; }
}
public class EmployeeInternal : Employee
{
public DateTime? HireDate { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
</p>
<p> </p>
<p>En al definición de las clases pueden observarse dos detalles:</p>
<ol>
<li>la clase base se define como <strong>abstract</strong>, este impedirá crear instancias del tipo base, con lo cual se obliga a crear instancias solo de los tipos derivados.</li>
<li>las clases hijas poseen propiedades concretas para cada tipo que definen características determinadas, es recomendable que las propiedades de las clases derivadas permitan nulos o sino asignarle un valor por default.</li>
</ol>
<p>La idea es poder mapear el modelo de objetos como el siguiente</p>
<p><a href="http://lh4.ggpht.com/-SItVVsqILnY/Uiz4L_MMHGI/AAAAAAAABRQ/LxQ1nTjjZSg/s1600-h/image%25255B5%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-M7ut6YvT5YI/Uiz4MvikbPI/AAAAAAAABRY/nfnLcr5IE5g/image_thumb%25255B3%25255D.png?imgmax=800" width="437" height="452" /></a></p>
<p>generando una tabla como ser</p>
<p><a href="http://lh6.ggpht.com/-NqOm2Ie-t9U/Uiz4NJrtBNI/AAAAAAAABRg/PFdSyyEG4vg/s1600-h/image%25255B11%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-OxMw9JpYaRI/Uiz4NvYB9aI/AAAAAAAABRo/HMIsGpYfflQ/image_thumb%25255B7%25255D.png?imgmax=800" width="397" height="379" /></a></p>
<p> </p>
<p><strong>Definición del Mapping
<hr /></strong></p>
<p>Definir como se debe persistir este tipo de modelo es bastante simple y no difiere a lo ya aprendido en los anteriores artículos que realice sobre el tema.</p>
<p>Se define el contexto de EF</p>
<p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:8dd6fb5b-3e51-4bba-9f6d-04988b5d0924" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new EmployeeMap());
base.OnModelCreating(modelBuilder);
}
}
public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
HasKey(x => x.EmployeeID);
Property(x => x.LastName).HasMaxLength(20).IsRequired();
Property(x => x.FirstName).HasMaxLength(10).IsRequired();
Property(x => x.Address).HasMaxLength(60);
Property(x => x.City).HasMaxLength(15);
Property(x => x.Region).HasMaxLength(15);
Property(x => x.PostalCode).HasMaxLength(10);
Property(x => x.Country).HasMaxLength(15);
Map<EmployeeInternal>(x => x.Requires("Type")
.HasValue("I")
.HasColumnType("char")
.HasMaxLength(1));
Map<EmployeeExternal>(x => x.Requires("Type")
.HasValue("E"));
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
</p>
<p> </p>
<p>Para la definición de la  herencia se debe puntualizar estas líneas:</p>
<p><a href="http://lh4.ggpht.com/-qlBdLrZOgRg/Uiz4OLvfPDI/AAAAAAAABRw/_W7VzQr7ono/s1600-h/image%25255B18%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-_58YXXppUgw/Uiz4Ov4sbsI/AAAAAAAABR4/Zk9G9-sKSXs/image_thumb%25255B12%25255D.png?imgmax=800" width="474" height="384" /></a></p>
<p>En ellas se define el nombre del campo que actuara como discriminador del tipo, así como los valores que tomara para cada clase hija definida, opcionalmente se puede especificar el tipo y precisión de la columna.</p>
<p>Sino se especifica el tipo para la columna del discriminador Entity Framework usara valores por defecto, por lo que la columna podrías crearse como nvarchar(128), esto puede resultar de poca importancia, pero si solo se va a contener un único carácter se estaría desperdiciando espacio para ese campo.</p>
<p>El campo definido como discriminador no se define como propiedad en las clases de la entidad de dominio, ya que la propia instancia de la clase define el tipo en si mismo. </p>
<p> </p>
<p><strong>Definición Repository
<hr /></strong></p>
<p>La definición del repositorio tiene algunas novedades respecto a los artículos anteriores, en este caso junto al proyecto que de entidades se define la interfaz para poder extender el repositorio.</p>
<p>Quizás en este momento no se aprecie este tipo de implementación, pero si se hace uso de algún framework de IoC (Invertion of Control) como ser Ninject, Unity, etc, allí si se requieren interfaces para poder desacoplar la creación del repositorio concreto.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:90a96207-619c-4ea7-b793-31fc1c87dbdc" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public interface IEmployeeRepository : IRepository<Employee>
{
List<EmployeeInternal> GetAllInternalType();
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:574d47c5-c9c3-4bbc-8887-65546160f16d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
/// <summary>
/// Retorna todos los empleados externos a la empresa
/// </summary>
/// <returns></returns>
public List<EmployeeInternal> GetAllInternalType()
{
using (NorthWindContext context = new NorthWindContext())
{
return context.Employees.OfType<EmployeeInternal>().ToList();
}
}
}
public class EmployeeInternalRepository : BaseRepository<EmployeeInternal>
{
}
public class EmployeeExternalRepository : BaseRepository<EmployeeExternal>
{
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong>Test – Inicializar datos
<hr /></strong></p>
<p>En si mismo la inicialización de los datos no son un test, pero como todos los test harán uso de un mismo conjunto de datos se podría decir que la inicialización también aplica pruebas en al creación de las entidades.</p>
<p>La ejecución de esta inicialización implica la validación de los métodos de creación de las entidades.</p>
<p> </p>
<p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:7d897c30-880b-4f3d-a760-90f14a6ff994" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">private void InitializeTestData()
{
IEmployeeRepository repoEmployee = new EmployeeRepository();
//
// elimino registros previos
//
List<Employee> list = repoEmployee.GetAll();
list.ForEach(x => repoEmployee.Delete(x));
//
// creo un empleado interno
//
employee1 = new EmployeeInternal()
{
FirstName = "name1",
LastName = "lastname1",
HireDate = DateTime.Now.AddMonths(-10)
};
repoEmployee.Create(employee1);
//
// creo un empleado externo
//
employee2 = new EmployeeExternal()
{
FirstName = "name2",
LastName = "lastname2",
ConsultantName = "ConsultantName2",
ContactExpiration = DateTime.Now.AddYears(2)
};
repoEmployee.Create(employee2);
//
// creo otro empleado externo
//
employee3 = new EmployeeExternal()
{
FirstName = "name3",
LastName = "lastname3",
ConsultantName = "ConsultantName3",
ContactExpiration = DateTime.Now.AddYears(1)
};
repoEmployee.Create(employee3);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
</p>
<p>La inicialización de los datos ejecuta instrucciones sql donde se pueden observar el campo definido como discriminador</p>
<p><a href="http://lh4.ggpht.com/-KJC8-1Le4nQ/Uiz4PUoeEBI/AAAAAAAABSA/QhS1XT-2RxY/s1600-h/image%25255B25%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/--hAymc0-_QQ/Uiz4QBapBvI/AAAAAAAABSI/pZAltzqiGxc/image_thumb%25255B17%25255D.png?imgmax=800" width="773" height="286" /></a></p>
<p> </p>
<p><strong>Test – Obtener todos los empleados
<hr /></strong></p>
<p>Obtendremos la lista de todos los empleados pudiendo validar el tipo de cada uno de ellos.</p>
<p> </p>
<p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:76bc7e5d-4759-4224-8a05-f65f19d7bd8d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAll_Employee()
{
InitializeTestData();
//
//recupero todos los empleados
//
IEmployeeRepository repoEmployee = new EmployeeRepository();
List<Employee> listIntEmployee = repoEmployee.GetAll();
//
// Assert
//
Assert.AreEqual(listIntEmployee.Count, 3);
Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
Assert.IsInstanceOfType(listIntEmployee[1], typeof(EmployeeExternal));
Assert.IsInstanceOfType(listIntEmployee[2], typeof(EmployeeExternal));
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
</p>
<p><a href="http://lh4.ggpht.com/-YIzinZVP5KI/Uiz4Qsu1eZI/AAAAAAAABSM/Nkz-QJXa57I/s1600-h/image%25255B31%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-rpT0n6F4sdQ/Uiz4RQA7n0I/AAAAAAAABSY/rqcdJemvv7U/image_thumb%25255B21%25255D.png?imgmax=800" width="594" height="213" /></a></p>
<strong></strong>
<p>El query creado para recuperar todos los empleados incluye un filtro que especifica todos los tipos existentes.</p>
<p> </p>
<p><strong>Test – Recuperar todos los empleados de tipo interno, usando un repositorio especifico
<hr /></strong></p>
<p>Se recuperan las entidades que corresponden a empleados propios de la empresa, pero para lograrlo se hace uso del repositorio definido para ese tipo concreto.</p>
<p>Se define un repositorio concreto para la clase <em>EmployeeInternal</em>, pudiendo utilizar los métodos que define el <em>RepositorioBase</em><> </p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:90a34d48-84fa-4fe5-aa19-65b6ab977c95" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAllInternal_UsingSpecificRepository_Employee()
{
InitializeTestData();
//
//recupero solo empleados internos
//
IRepository<EmployeeInternal> repoInternalEmployee = new EmployeeInternalRepository();
List<EmployeeInternal> listIntEmployee = repoInternalEmployee.GetAll();
//
// Assert
//
Assert.AreEqual(listIntEmployee.Count, 1);
Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
Assert.AreEqual(listIntEmployee[0].FirstName, employee1.FirstName);
Assert.IsNotNull(listIntEmployee[0].HireDate);
Assert.AreEqual(listIntEmployee[0].HireDate.Value.ToShortDateString(), employee1.HireDate.Value.ToShortDateString());
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><a href="http://lh5.ggpht.com/-V5zVL0EdaU4/Uiz4RjmyCfI/AAAAAAAABSg/RWHZEEOfEoM/s1600-h/image%25255B37%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-4qucrEFteJ8/Uiz4SLSeGPI/AAAAAAAABSo/i4WjXK2sksc/image_thumb%25255B25%25255D.png?imgmax=800" width="577" height="184" /></a></p>
<p>Al recuperar todas las instancia para un tipo en concreto la query filtra por el identificado definido para ese tipo.</p>
<p>Seguramente se preguntaran que significa el </p>
<p><em>'0X0X' AS [C1],</em></p>
<p>esa línea es usada internamente por Entity Framework para saber la instancia de que tipo en concreto tiene que materializar, o sea es una marca que define el tipo.</p>
<p><strong></strong></p>
<p><strong>Test – Recuperar todos los empleados del tipo interno, usando funcionalidad del repositorio genérico
<hr /></strong></p>
<p>Se recuperan las entidades que corresponden a empleados propios de la empresa, pero en este caso se utilizara el repositorio definido para la clase base.</p>
<p>Es por medio del OfType<> que se especifica que tipo concreto se quiere recuperar.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2b7fb5fe-b2ca-4732-ad07-d3684d5269d4" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAllInternal_UsingGenericRepository_Employee()
{
InitializeTestData();
//
//recupero solo empleados internos
//
IEmployeeRepository repoEmployee = new EmployeeRepository();
List<EmployeeInternal> listIntEmployee = repoEmployee.GetAllInternalType();
//
// Assert
//
Assert.AreEqual(listIntEmployee.Count, 1);
Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
Assert.AreEqual(listIntEmployee[0].FirstName, employee1.FirstName);
Assert.IsNotNull(listIntEmployee[0].HireDate);
Assert.AreEqual(listIntEmployee[0].HireDate.Value.ToShortDateString(), employee1.HireDate.Value.ToShortDateString());
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><a href="http://lh6.ggpht.com/-NGZzTinLS4s/Uiz4SjFLp6I/AAAAAAAABSw/gCx8tNlYH2s/s1600-h/image%25255B43%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-FbPIasZ0I4o/Uiz4TOCNaNI/AAAAAAAABS4/IY_uAIYA8nQ/image_thumb%25255B29%25255D.png?imgmax=800" width="478" height="199" /></a></p>
<p>Usar el OfType<> genera el mismo resultado que especializar el repositorio, la query generada son idénticas</p>
<p> </p>
<p><strong>Test – Obtener todos los empleados Externos
<hr /></strong></p>
<p>Se obtienes todos los empleados de contratación externa.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:18f8d140-f856-4183-9311-c95fdb35dc48" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAllExternal_Employee()
{
InitializeTestData();
//
//recupero solo empleados externos
//
IRepository<EmployeeExternal> repoExternalEmployee = new EmployeeExternalRepository();
List<EmployeeExternal> listExtEmployee = repoExternalEmployee.GetAll();
//
// Assert
//
Assert.AreEqual(listExtEmployee.Count, 2);
Assert.IsInstanceOfType(listExtEmployee[0], typeof(EmployeeExternal));
Assert.IsInstanceOfType(listExtEmployee[1], typeof(EmployeeExternal));
Assert.AreEqual(listExtEmployee[0].FirstName, employee2.FirstName);
Assert.IsNotNull(listExtEmployee[0].ContactExpiration);
Assert.AreEqual(listExtEmployee[0].ContactExpiration.Value.ToShortDateString(), employee2.ContactExpiration.Value.ToShortDateString());
Assert.AreEqual(listExtEmployee[0].ConsultantName, employee2.ConsultantName);
Assert.AreEqual(listExtEmployee[1].FirstName, employee3.FirstName);
Assert.IsNotNull(listExtEmployee[1].ContactExpiration);
Assert.AreEqual(listExtEmployee[1].ContactExpiration.Value.ToShortDateString(), employee3.ContactExpiration.Value.ToShortDateString());
Assert.AreEqual(listExtEmployee[1].ConsultantName, employee3.ConsultantName);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><a href="http://lh4.ggpht.com/-pFaMiNw4t8I/Uiz4ToI1X6I/AAAAAAAABTA/JrnLXubCeQk/s1600-h/image%25255B55%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-RuFH2vJAQEA/Uiz4UDaa4kI/AAAAAAAABTI/gkLFLu8PCcM/image_thumb%25255B37%25255D.png?imgmax=800" width="516" height="250" /></a></p>
<p> </p>
<p> </p>
<p><strong>Poder crear instancias de la clase base
<hr /></strong></p>
<p>Al comienzo del artículo comente que la clase base se define con abstract para así forzar siempre usar las derivadas, al generar la tabla el campo que actúa como discriminador no permita nulo.</p>
<p>Ahora si queremos crear instancias de la clase base, solo será cuestión de permitirlo quitando el abstract.</p>
<p> </p>
<p><a href="http://lh5.ggpht.com/-6XJyvXgCBOE/Uiz4Uta26TI/AAAAAAAABTQ/kDyTrjs30R0/s1600-h/image%25255B64%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-DKPiFkZkPMk/Uiz4VUKaKLI/AAAAAAAABTY/0oIRRDetl-E/image_thumb%25255B42%25255D.png?imgmax=800" width="373" height="266" /></a></p>
<p>la tabla generada por EF ahora permite nulo en el campo “Type”</p>
<p><a href="http://lh5.ggpht.com/-5QWZqt1cOHo/Uiz4V8MJHDI/AAAAAAAABTg/4JXBFW8NPjI/s1600-h/image%25255B67%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-D7sU5HoWiw0/Uiz4WvIM0UI/AAAAAAAABTo/S20v-9CrF6A/image_thumb%25255B45%25255D.png?imgmax=800" width="370" height="354" /></a></p>
<p>con lo cual se podrán crear instancias del tipo base “<em>Employee</em>”, para probarlo creamos un test</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2ba5ba2c-81e2-42d1-b1b1-de3b7a4c7142" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetAll_WithBaseType_Employee()
{
IEmployeeRepository repoEmployee = new EmployeeRepository();
//
// creo un empleado interno
//
EmployeeInternal employee1 = new EmployeeInternal()
{
FirstName = "name1",
LastName = "lastname1",
HireDate = DateTime.Now.AddMonths(-10)
};
repoEmployee.Create(employee1);
//
// creo un empleado interno
//
Employee employee2 = new Employee()
{
FirstName = "name2",
LastName = "lastname2"
};
repoEmployee.Create(employee2);
//
//recupero todos los empleados
//
List<Employee> listIntEmployee = repoEmployee.GetAll();
//
// Assert
//
Assert.AreEqual(listIntEmployee.Count, 2);
Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
//validamos los tipos base de cada objeto recuperado
Assert.AreEqual(listIntEmployee[0].GetType().BaseType, typeof(Employee));
Assert.AreEqual(listIntEmployee[1].GetType().BaseType, typeof(object));
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Al final del test se valida los tipo base de cada instancia, para el empleado interno será la clase “Employee”, pero para una instancia base del empleado al no derivar de ninguna otra será el tipo “object”.</p>
<p>El query generado en este caso es bastante mas complejo</p>
<p><a href="http://lh6.ggpht.com/-12Fffd_s-qc/Uiz4XcAIhAI/AAAAAAAABTw/jzdpLFkGXkk/s1600-h/image%25255B85%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-K9IhQXNBJyc/Uiz4YFYGD5I/AAAAAAAABT4/K8yZ206msj4/image_thumb%25255B57%25255D.png?imgmax=800" width="754" height="192" /></a></p>
<p>por eso de ser posible definir la herencia para usar solo las clases hijas</p>
<p> </p>
<p><strong>Campos discriminador numérico
<hr /></strong></p>
<p>Además de definir un campo discriminador del tipo string o char, también se puede definir numérico, solo hay que especificar los valores en el mapping</p>
<p><a href="http://lh3.ggpht.com/-mr4F7KbYmQg/Uiz4YkMQx4I/AAAAAAAABUA/SBh0lB9LAJo/s1600-h/image%25255B79%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-7QlXEfm1mE0/Uiz4ZWXWw9I/AAAAAAAABUE/iuGvLd_iD7A/image_thumb%25255B53%25255D.png?imgmax=800" width="396" height="291" /></a></p>
<p>la creación de la tabla cambiara el campo “Type” como numérico</p>
<p><a href="http://lh3.ggpht.com/-H5Mg8DMCM-s/Uiz4Zw8_1vI/AAAAAAAABUM/I_binTIOGWo/s1600-h/image%25255B73%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-lOZTm_oQZfs/Uiz4aqcDUeI/AAAAAAAABUY/Ka8pYZBJRrI/image_thumb%25255B49%25255D.png?imgmax=800" width="381" height="363" /></a></p>
<p> </p>
<p><strong>Documentación de referencia </strong>
<hr /></p>
<h4><font style="font-weight: normal"><font size="2"><a href="http://weblogs.asp.net/manavi/archive/2010/12/24/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph.aspx" target="_blank">Inheritance with EF Code First: Part 1 – Table per Hierarchy (TPH)</a></font> </font></h4>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21402&authkey=AKbzg2nuceQIqDA" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com15tag:blogger.com,1999:blog-7361892840793499128.post-69567394549158467522013-07-28T22:26:00.001-07:002013-08-24T16:42:25.943-07:00[Entity Framework][Code First] Asociación mucho a muchos<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>Al momento de relacionar entidades algunas pueden necesitar asociarse con colecciones de otras entidades, pero que sucede cuando esto se necesita en ambas direcciones?, es aquí donde entran las relaciones mucho a muchos, en donde a nivel de base de datos se va a requerir de una tabla intermedia para poder implementar la relación. </p> <p>En este articulo veremos como lograr esta asociación y analizaremos que pasa a nivel de base de datos, el objetivo final será lograr un modelo de objeto como el siguiente:</p> <p><a href="http://lh4.ggpht.com/-XKF5B8l1gho/UfX84ZHtJgI/AAAAAAAABNw/nvaLldwx9fo/s1600-h/image41.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-bHltfrcuE_k/UfX84xRhqAI/AAAAAAAABN4/1CMP7XKnfVM/image_thumb23.png?imgmax=800" width="522" height="268" /></a></p> <p>y su equivalente en el modelo de datos:</p> <p><a href="http://lh5.ggpht.com/-EF1tA4z1EbA/UfX85WDaGhI/AAAAAAAABOA/N7eeou9xY3U/s1600-h/image34.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-umq5xHB33rc/UfX851kocvI/AAAAAAAABOI/KH3LJSbWADU/image_thumb18.png?imgmax=800" width="687" height="326" /></a></p> <p> </p> <p><strong>Definición de las entidades </strong></p> <hr /> <p>En el proyecto “Entities” serán definidas las clases que modelan los empleados y territorios, cada uno define una propiedad del tipo colección hacia a otra.</p> <p> </p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a1ea3af3-1b1d-4115-82e2-5c39dae63827" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Territory
{
public int TerritoryID { get; set; }
public string TerritoryDescription { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:8b17126c-bf7b-4664-bd83-b1136a059a24" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public virtual ICollection<Territory> Territories { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p><strong>Definición del contexto </strong></p>
<hr />
<p>La clase de context, la cual hereda de Dbcontext, define las propiedades que representan cada entidad, utilizando el tipo DbSet<>.</p>
<p>La sobrecarga del método OnModelCreating será el responsable de la configuración del mapping, para que no quede toda el código junto en un solo sitio y sea mantenible en el tiempo se hará uso de clases separadas para cada entidad, estas clases heredan de EntityTypeConfiguration<>.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:06964b88-3b31-4e59-9e9f-0a8cee51ed0a" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Territory> Territories { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TerritoryMap());
modelBuilder.Configurations.Add(new EmployeeMap());
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>En este caso se decidió utilizar la entidad del territorio para definir la relación mucho a muchos, esto se logra definiendo el HasMany()</p>
<p>La relación requiere definir el nombre que tendrá la tabla intermedia, así como los campos claves que actuaran en la relación.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:29ab47ad-1438-4753-9e99-6c4a8c80c4a8" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class TerritoryMap : EntityTypeConfiguration<Territory>
{
public TerritoryMap()
{
HasKey(x => x.TerritoryID);
Property(x => x.TerritoryDescription).HasColumnType("nchar").HasMaxLength(50).IsRequired();
HasMany(x => x.Employees)
.WithMany(x => x.Territories)
.Map(mc =>
{
mc.MapLeftKey("TerrytoryID");
mc.MapRightKey("EmployeeID");
mc.ToTable("EmployeeTerritories");
});
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Nota: la definición de la relación no era obligatorio hacerlo en la entidad Territory, se podría haber realizado en la clase Map del Employee sin ningún problema, solo hay que tener en cuenta cambiaran las definiciones de MapLeftKey() y MapRighKey()</p>
<p>El empleado define el mapeo normalmente</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a585c231-1a25-4ce5-b95c-d2a261cc29b1" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
HasKey(x => x.EmployeeID);
Property(x => x.LastName).HasMaxLength(20).IsRequired();
Property(x => x.FirstName).HasMaxLength(10).IsRequired();
Property(x => x.Address).HasMaxLength(60);
Property(x => x.City).HasMaxLength(15);
Property(x => x.Region).HasMaxLength(15);
Property(x => x.PostalCode).HasMaxLength(10);
Property(x => x.Country).HasMaxLength(15);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong>Especialización del repositorio </strong>
<hr /></p>
<p>Para poder operar con las colecciones se requiere estar dentro del contexto de EF, pero sucede que la implementación del repositorio desconecta las instancias de las entidades al devolverás para poder ser usadas desde fuera por lo tanto aplicar cambios en estas no ejecutara las acciones de actualización de forma correcta.</p>
<p>Es aquí donde se requiere personalizar el repositorio, por ejemplo en el repositorio del territorio definir métodos que permiten agregar o quitar empleados.</p>
<p>Si bien los parámetros de estos métodos son tipos de cada entidad, se podrían haber utilizado solo los IDs de las mismas</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:af51c893-b238-4198-b302-dbea33a7a090" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class TerritoryRepository : BaseRepository<Territory>
{
public void AddEmployees(Territory territory, List<Employee> employes)
{
using (NorthWindContext context = new NorthWindContext())
{
//marcamos el territorio para que no reciba cambios
context.Entry(territory).State = EntityState.Unchanged;
if (territory.Employees == null)
territory.Employees = new List<Employee>();
//recorremos cada empleado que se quiera asociar
employes.ForEach(x =>
{
//el empleado tampoco debe recibir cambios
context.Entry(x).State = EntityState.Unchanged;
//asociamos a la colecion de empleados del territorio el nuevo item
//este si recibira cambios
territory.Employees.Add(x);
});
context.SaveChanges();
}
}
public void RemoveEmployees(Territory territory, List<Employee> employees)
{
//validamos que haya algo que remover
if (employees == null || employees.Count == 0)
return;
using (NorthWindContext context = new NorthWindContext())
{
//recuperamos el terrotorio y sus empleados
//esto es necesario porque el objeto donde se debe remover tiene que estar dentro del contexto de EF
Territory territorySel = context.Set<Territory>().Include("Employees").FirstOrDefault(x => x.TerritoryID == territory.TerritoryID);
if (territory.Employees == null || territory.Employees.Count == 0)
return;
employees.ForEach(x =>
{
//localizamos al empleado dentro de la coleccion que se recupero anteriormente
Employee employeeRemove = territorySel.Employees.First(e => e.EmployeeID == x.EmployeeID);
//se remueve de la coleccion haciendo uso de la instancia
territorySel.Employees.Remove(employeeRemove);
});
context.SaveChanges();
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong></strong></p>
<p><strong>Test – Obtener territorio con empleados asociados </strong>
<hr /></p>
<p>Empezaremos con un test simple, en donde solo se relacionen las entidades y puedan recuperarse </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d9f5eac5-2864-48c2-9ae8-43576a395437" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Get_ListEmployee_Territory()
{
TerritoryRepository repoTerritory = new TerritoryRepository();
Territory territoryNew = new Territory()
{
TerritoryDescription = "territoty 1",
Employees = new List<Employee>()
{
new Employee() { FirstName = "Name 1", LastName ="LastaName 1" },
new Employee() { FirstName = "Name 2", LastName ="LastaName 2" }
}
};
repoTerritory.Create(territoryNew);
var territorySel = repoTerritory.Single(x => x.TerritoryID == territoryNew.TerritoryID,
new List<Expression<Func<Territory,object>>>(){ x=>x.Employees });
Assert.IsNotNull(territorySel);
Assert.IsNotNull(territorySel.Employees);
Assert.AreEqual(territorySel.Employees.Count, 2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La creación del territorio implicara varias acciones como puede observarse en la captura del profiler</p>
<p><a href="http://lh4.ggpht.com/-Nca0cnctU3A/UfX86V0dC1I/AAAAAAAABOQ/B_gO_sYgPnk/s1600-h/image58.png" target="_blank"><img title="image" style="margin: 0px; display: inline" alt="image" src="http://lh4.ggpht.com/-PhKhLA_tPRE/UfX87BdWGLI/AAAAAAAABOY/N2mo6qgiSxM/image_thumb37.png?imgmax=800" width="672" height="328" /></a></p>
<p>Se insertan los empleados, a continuación el territorio y por ultimo los registros que relacionan ambas entidades</p>
<p>Para recuperar las relaciones es necesario definir la propiedad como parte del Include() </p>
<p><a href="http://lh5.ggpht.com/-n37IQOM_HZ4/UfX88AU_KUI/AAAAAAAABOg/Ij7_poMgiDc/s1600-h/image62.png" target="_blank"><img title="image" style="margin: 0px; display: inline" alt="image" src="http://lh6.ggpht.com/-JQbeEqoHm5Q/UfX88jb6bpI/AAAAAAAABOo/dcW1w7zgi3E/image_thumb38.png?imgmax=800" width="683" height="256" /></a></p>
<p>En una sola instrucción SELECT se recuperan el territorio y los empleados asociados a esta.</p>
<p> </p>
<p><strong>Test – Agregar empleados a territorio existentes </strong>
<hr /></p>
<p>Seria muy raro que al momento de crear un territorio se disponga de los empleados que se asocian al mismo, y solo estos conformen la relación, lo común es tener entidades independientes y asociarlas de forma dinámica, por lo general mediante acciones del usuario en la aplicación.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:5bd301ce-9c43-46b0-a516-e91d26f56543" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Update_ExistingEmployee_Territory()
{
TerritoryRepository repoTerritory = new TerritoryRepository();
EmployeeRepository repoEmployee = new EmployeeRepository();
//se crean los empleados
Employee employeeNew1 = new Employee() {
FirstName = "Name 1",
LastName ="LastaName 1"
};
repoEmployee.Create(employeeNew1);
Employee employeeNew2 = new Employee() {
FirstName = "Name 2",
LastName ="LastaName 2"
};
repoEmployee.Create(employeeNew2);
//se crea el territorio
Territory territoryNew = new Territory()
{
TerritoryDescription = "territoty 1"
};
repoTerritory.Create(territoryNew);
//asignamos los empleados al territorio existente
repoTerritory.AddEmployees(territoryNew, new List<Employee>(new Employee[]{ employeeNew1, employeeNew2 }));
//validamos que la asignacion se haya realizado correctamente
//recuperando la entidad y sus relaciones
var territorySel = repoTerritory.Single(x => x.TerritoryID == territoryNew.TerritoryID,
new List<Expression<Func<Territory, object>>>() { x => x.Employees });
Assert.IsNotNull(territorySel);
Assert.IsNotNull(territorySel.Employees);
Assert.AreEqual(territorySel.Employees.Count, 2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La primer parte de la ejecución del test crea las entidades de forma independiente</p>
<p><a href="http://lh4.ggpht.com/-Raehqr-Shpk/UfX89gG0mmI/AAAAAAAABOw/U6fC3fQMjZQ/s1600-h/image%25255B12%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-t8AE-uTinqw/UfX8-UO3UWI/AAAAAAAABO4/s9MyBxQf4FU/image_thumb%25255B7%25255D.png?imgmax=800" width="703" height="329" /></a></p>
<p>Luego se añade los empleados al territorio</p>
<p><a href="http://lh6.ggpht.com/-ZkKwVWp-WMY/UfX8-_oZozI/AAAAAAAABPA/1ZUotPXiGqo/s1600-h/image%25255B18%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-VUF2hYKeH6M/UfX8_gKjWOI/AAAAAAAABPI/fNYC02OBTaw/image_thumb%25255B11%25255D.png?imgmax=800" width="688" height="178" /></a></p>
<p>y al final para validar que todo este correcto recuperando el territorio y su relación con los empleados</p>
<p><a href="http://lh6.ggpht.com/-O4ASztB8GHs/UfX9AYxrz4I/AAAAAAAABPQ/gotL-Za-iqE/s1600-h/image%25255B24%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-YszPku1gSx8/UfX9BEszRFI/AAAAAAAABPY/w3KMR5WUAn8/image_thumb%25255B15%25255D.png?imgmax=800" width="701" height="288" /></a></p>
<p> </p>
<p><strong>Test – Remover un empleado asociado </strong>
<hr /></p>
<p>Hasta el momento hemos agregando entidades a la colección, pero es muy común querer quitarlas, para esto haremos uso del método creado especialmente en el repositorio de la entidad Territorio.</p>
<p>Antes de seguir hay que aclarar que la estructura de las tablas que crea Entity Framework desde el modelo asigna en las relaciones la opción de borrado en cascada, es necesario conocer esto si es que se quiere eliminar una entidad completa, ya que el hacerlo provocara que todas las asociaciones que esta tenga también sean eliminadas</p>
<p> </p>
<p><a href="http://lh5.ggpht.com/-YxF_dlOsbv4/UfX9Biet8DI/AAAAAAAABPg/LQhch6LAeGs/s1600-h/image50.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-jZT8-WXB5FY/UfX9CeatijI/AAAAAAAABPo/30Zmdy0yy4k/image_thumb32.png?imgmax=800" width="678" height="183" /></a></p>
<p> </p>
<p><a href="http://lh5.ggpht.com/-0uAV2TZ5SQw/UfX9C3wwU3I/AAAAAAAABPw/SFBRUXsouxE/s1600-h/image51.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-gGaEuVEs_y4/UfX9DqmExeI/AAAAAAAABP4/nOjPVSt4W_w/image_thumb33.png?imgmax=800" width="680" height="302" /></a></p>
<p>Veamos el código del test que remueve un empleado en concreto de la colección </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2ba38009-9705-458d-ae66-a3b37a747b4d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Delete_AssignedEmployee_Territory()
{
TerritoryRepository repoTerritory = new TerritoryRepository();
EmployeeRepository repoEmployee = new EmployeeRepository();
//se crean los empleados
Employee employeeNew1 = new Employee()
{
FirstName = "Name 1",
LastName = "LastaName 1"
};
repoEmployee.Create(employeeNew1);
Employee employeeNew2 = new Employee()
{
FirstName = "Name 2",
LastName = "LastaName 2"
};
repoEmployee.Create(employeeNew2);
//se crea el territorio
Territory territoryNew = new Territory()
{
TerritoryDescription = "territoty 1"
};
repoTerritory.Create(territoryNew);
//asignamos los empleados al territorio existente
repoTerritory.AddEmployees(territoryNew, new List<Employee>(new Employee[] { employeeNew1, employeeNew2 }));
//validamos que la asignacion se haya realizado correctamente
//recuperando la entidad y sus relaciones
var territorySel = repoTerritory.Single(x => x.TerritoryID == territoryNew.TerritoryID,
new List<Expression<Func<Territory, object>>>() { x => x.Employees });
Assert.IsNotNull(territorySel);
Assert.IsNotNull(territorySel.Employees);
Assert.AreEqual(territorySel.Employees.Count, 2);
//removemos uno de los empleados asignados
repoTerritory.RemoveEmployees(territoryNew, new List<Employee>(new Employee[] { employeeNew1 }));
//recuperamos el territorio para validar que se haya eliminado el empleado
var territorySel2 = repoTerritory.Single(x => x.TerritoryID == territoryNew.TerritoryID,
new List<Expression<Func<Territory, object>>>() { x => x.Employees });
Assert.IsNotNull(territorySel2);
Assert.IsNotNull(territorySel2.Employees);
Assert.AreEqual(territorySel2.Employees.Count, 1);
Employee employeeSel = territorySel2.Employees.First();
Assert.AreEqual(employeeSel.FirstName, employeeNew2.FirstName);
Assert.AreEqual(employeeSel.LastName, employeeNew2.LastName);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La primer parte es idéntica al Test anterior, se crean las entidades individualmente, se asignan los empleados al territorio y se validad que todo se haya realizado correctamente.</p>
<p>Luego se procede a quitar uno de los empleados, ejecutándose la instrucción SQL que remueve el registro </p>
<p><a href="http://lh4.ggpht.com/-balF8dk2y0g/UfX9D7lPxzI/AAAAAAAABQA/1JhFXJeR2bw/s1600-h/image%25255B30%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-P11ITXPHuiU/UfX9Et2ibwI/AAAAAAAABQI/rsEUY_sIen0/image_thumb%25255B19%25255D.png?imgmax=800" width="713" height="150" /></a></p>
<p> </p>
<p><strong>Documentación de referencia </strong>
<hr /></p>
<p><a href="http://weblogs.asp.net/manavi/archive/2011/05/17/associations-in-ef-4-1-code-first-part-6-many-valued-associations.aspx" target="_blank">Associations in EF Code First: Part 6 – Many-valued Associations</a> </p>
<p><a href="http://msdn.microsoft.com/es-es/data/jj591620#ManyToMany">Configuring a Many-to-Many Relationship</a></p>
<p> </p>
<p><strong>Código </strong></p>
<hr />
<p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF.</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21392&authkey=AKumR6hyxdETIIE" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com42tag:blogger.com,1999:blog-7361892840793499128.post-21838281136574414822013-07-04T10:40:00.001-07:002013-07-04T10:40:08.947-07:00[Entity Framework][Code First] Asociación uno a muchos (3/3)<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Analizaremos como implementar las acciones que permiten eliminar una entidad y sus relaciones, pudiendo además por medio de las opciones de EF cambiar el mapping de la entidad para lograr cambiar las opciones de eliminar en cascada entre las tablas.</p> <p>Otro tema que veremos es como asociar las entidades a través de su instancia. Hasta el momento la relación de una entidad con otra existente se realiza asignando la propiedad ID, o sea si un producto tiene una relación con una categoría se asigna la propiedad CategoryID, cuando se asignaba la propiedad Category de la entidad Product se creaba una nueva categoría lo cual puede que no se quiere que suceda. </p> <p>Vamos a analizar de que forma podemos modificar el código para indicar que entidad asociada no se debe volver a crear.</p> <p>En resumen los temas que se trataran serán: </p> <ol> <li>Eliminar en cascada </li> <li>Asociar mediante la instancia de la entidad </li> </ol> <p>Este artículo es la continuación de: </p> <p><a href="http://ltuttini.blogspot.com.ar/2013/07/entity-frameworkcode-first-asociacion.html">[Entity Framework][Code First] Asociación uno a mucho (1/3)</a></p> <p> </p> <p><strong>1- Eliminar en cascada </strong></p> <hr /> <p>Al seguir las convenciones en la definición de relaciones EF determina las acción de eliminar en cascada al crear las relaciones entre las tablas.</p> <p>Si definimos una relación como obligatoria marcara automáticamente para permitir eliminar en cascada, salvo que se indique lo contrario.</p> <p>En cambio si definimos una relación como opcional mediante la definición de un tipo null (en la propiedad asignada para el Foreign Key) en este caso EF no especificara ninguna acción.</p> <p> </p> <p><strong>1.1- Asociación obligatoria – Eliminar en cascada </strong> <hr /></p> <p>Si definimos las entidades usando</p> <p><a href="http://lh6.ggpht.com/-hsd25Y86uv0/UdWzAWKfg6I/AAAAAAAABJM/5htHI7X6uww/s1600-h/image5.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-yg__Z5Dt_d4/UdWzBG-ocEI/AAAAAAAABJU/OvRMwyNnQ9g/image_thumb3.png?imgmax=800" width="326" height="298" /></a></p> <p>en este caso por seguir las convenciones podríamos o no definir la línea en el context</p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:c71e4acb-ff23-4929-8886-30737a475f70" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">HasRequired(x => x.Category)
.WithMany(x => x.Products)
.HasForeignKey(x => x.CategoryID)
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<br />
<p>se crearan las tablas relacionándolas con al opción de cascada activa</p>
<p><a href="http://lh3.ggpht.com/-W7BsRAc6JFw/UdWzB1wixyI/AAAAAAAABJc/P4dZJxkLG5k/s1600-h/image1%25255B1%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-o-bAnGHhN9k/UdWzCy6RVKI/AAAAAAAABJk/K8Z86WJvxIs/image1_thumb.png?imgmax=800" width="630" height="354" /></a></p>
<p>si ejecutamos un test como ser</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:bc2dd1e7-a71c-4a5e-896c-48ae65d2b97f" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Delete_Cascade_Category()
{
ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea el producto relacionado con la categoria
Product productNew1 = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
CategoryID = categoryNew.CategoryID
};
repoProduct.Create(productNew1);
Product productNew2 = new Product()
{
ProductName = "prod 2",
UnitPrice = 12,
Discontinued = false,
CategoryID = categoryNew.CategoryID
};
repoProduct.Create(productNew2);
//elimina la categoria y sus productos asociados
repoCategory.Delete(categoryNew);
//se recupera la categoria para validar si se elimino
var categorySelected = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID);
Assert.IsNull(categorySelected);
//se recupera el primer producto
Product productSel1 = repoProduct.Single(x=>x.ProductID == productNew1.ProductID);
Assert.IsNull(productSel1);
//se recupera el primer producto
Product productSel2 = repoProduct.Single(x=>x.ProductID == productNew2.ProductID);
Assert.IsNull(productSel2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>se crea la categoría y sus dos productos relacionados</p>
<p><a href="http://lh6.ggpht.com/-_IuBsc3o-yY/UdWzEoMuLNI/AAAAAAAABJs/EV2euluXTBg/s1600-h/image32.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-h2pBFFbmlwo/UdWzFkD3-qI/AAAAAAAABJ0/_xZfgPHfEoM/image_thumb21.png?imgmax=800" width="640" height="232" /></a></p>
<p>y la operación de elimina</p>
<p><a href="http://lh6.ggpht.com/-gMByZylU_A4/UdWzGfwDMXI/AAAAAAAABJ8/wNBNhwK7ebw/s1600-h/image311.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-P5UIR3pByJQ/UdWzHFDNOqI/AAAAAAAABKE/voFBCuEWUnE/image_thumb20.png?imgmax=800" width="632" height="117" /></a></p>
<p>pero al estar activa en la relación la opción de cascada se eliminan los productos, por eso si analizamos el test veremos que tanto la categoría como los productos ya no existen</p>
<p> </p>
<p><a href="http://lh5.ggpht.com/-InwGnVmESv8/UdWzHhV5ZFI/AAAAAAAABKM/8hg8X7cmg34/s1600-h/SNAGHTML7ff1cd3d6.png" target="_blank"><img title="SNAGHTML7ff1cd3d" style="display: inline" alt="SNAGHTML7ff1cd3d" src="http://lh5.ggpht.com/-adAvBWfiHYU/UdWzIiGhQ2I/AAAAAAAABKU/RALX_RuAHto/SNAGHTML7ff1cd3d_thumb3.png?imgmax=800" width="530" height="224" /></a></p>
<p> </p>
<p><strong>1.2- Asociación obligatoria – Anular eliminar en cascada </strong></p>
<hr />
<p>Con el uso del método WillCascadeOnDelete() en la definición del contexto podremos cambiar la convención utilizada por EF cuando crea las relaciones entre las tablas.</p>
<p><a href="http://lh5.ggpht.com/-12GhaCMii-k/UdWzJVIWaHI/AAAAAAAABKc/VVzdJjVNasc/s1600-h/image33%25255B1%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/--15VkZcaRoQ/UdWzKn1vsWI/AAAAAAAABKk/Zxk7H3dtw_A/image33_thumb.png?imgmax=800" width="500" height="318" /></a></p>
<p>al ejecutar el mismo test del caso anterior, obtendremos un error </p>
<p><a href="http://lh6.ggpht.com/-iRLisr7irrw/UdWzLZDjVMI/AAAAAAAABKs/nWZzCwiFWis/s1600-h/SNAGHTML7ffbf289%25255B1%25255D.png" target="_blank"><img title="SNAGHTML7ffbf289" style="display: inline" alt="SNAGHTML7ffbf289" src="http://lh4.ggpht.com/-Z7W0IlJbBng/UdWzMNau8EI/AAAAAAAABK0/g5lJm6YbW5I/SNAGHTML7ffbf289_thumb.png?imgmax=800" width="560" height="270" /></a></p>
<p>por lo que ya no podremos eliminar la categoría si previamente no quitamos los productos asociados</p>
<p> </p>
<p><strong>1.3- Asociación opcional – Eliminar </strong>
<hr /></p>
<p>Al definir la propiedad de relación como nullable EF interpretara que una asociación como opcional </p>
<p><a href="http://lh3.ggpht.com/-CtCDQ7F0la8/UdWzMxAio8I/AAAAAAAABK8/wd6i43qjPtU/s1600-h/image19.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-LPP2v3PrOBU/UdWzOPr1EvI/AAAAAAAABLE/VMDtfTo7fwA/image_thumb12.png?imgmax=800" width="349" height="321" /></a></p>
<p>por seguir las convenciones podríamos o no definir la línea en el context</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:cee4d8f0-d744-40a5-8476-88d90f2e1f23" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">HasOptional(x => x.Supplier)
.WithMany(x => x.Products)
.HasForeignKey(x => x.SupplierID);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>se creara las relaciones entre las tablas en donde no se realiza ninguna acción al eliminar</p>
<p><a href="http://lh6.ggpht.com/-slayptJgwt4/UdWzO-ZxZLI/AAAAAAAABLM/G3sXUDCpEZg/s1600-h/image12.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-pcG4LzD6Vy0/UdWzPlYZoYI/AAAAAAAABLU/2Rp9RNx-FwQ/image_thumb7.png?imgmax=800" width="529" height="294" /></a></p>
<p>Podemos validarlo por medio de un test</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:26a661f7-6856-4edc-be71-bd540c5394ae" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Delete_Supplier()
{
ProductRepository repoProduct = new ProductRepository();
SupplierRepository repoSupplier = new SupplierRepository();
//se crea el proveedor
Supplier supplierNew = new Supplier()
{
CompanyName = "supplier 1"
};
repoSupplier.Create(supplierNew);
//se crea el producto relacionandolo con el proveedor
Product productNew1 = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
Category = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
},
SupplierID = supplierNew.SupplierID
};
repoProduct.Create(productNew1);
//se crea otro producto relacionandolo con el proveedor
Product productNew2 = new Product()
{
ProductName = "prod 2",
UnitPrice = 12,
Discontinued = false,
Category = new Category()
{
CategoryName = "category2",
Description = "desc category 2"
},
SupplierID = supplierNew.SupplierID
};
repoProduct.Create(productNew2);
//se recupera el proveedor para obtener los productos asociados
var supplierSel = repoSupplier.Single(x => x.SupplierID == supplierNew.SupplierID,
new List<Expression<Func<Supplier, object>>>() { x => x.Products });
//se elimina la categoria y se desasocian los productos
repoSupplier.Delete(supplierSel);
//se recupera la categoria para validar si se elimino
var categorySelected = repoSupplier.Single(x => x.SupplierID == supplierNew.SupplierID);
Assert.IsNull(categorySelected);
//se recupera el primer producto
Product productSel1 = repoProduct.Single(x => x.ProductID == productNew1.ProductID);
Assert.IsNotNull(productSel1);
//se recupera el primer producto
Product productSel2 = repoProduct.Single(x => x.ProductID == productNew2.ProductID);
Assert.IsNotNull(productSel2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Durante la ejecución se puede ir verificando las acciones que se realizan en la db, se crea el proveedor y los productos</p>
<p><a href="http://lh6.ggpht.com/-g4xuWmnoihU/UdWzQ3iNj6I/AAAAAAAABLc/A7tbcP2jqqk/s1600-h/image60.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-ccLd8kmwcNU/UdWzR18XXwI/AAAAAAAABLk/ayP7_fvHwH4/image_thumb39.png?imgmax=800" width="529" height="233" /></a></p>
<p>se recupera el proveedor y sus productos, esto es necesario ya que al realizar la eliminación los productos deben ser actualizados</p>
<p><a href="http://lh5.ggpht.com/-u849-6Zdcpw/UdWzS_NgmdI/AAAAAAAABLs/kACOouRBp8w/s1600-h/image61.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-gioHAMGcm-c/UdWzTk-nN6I/AAAAAAAABL0/9-7DJKGA5ac/image_thumb40.png?imgmax=800" width="527" height="272" /></a></p>
<p>ante de eliminar el proveedor se ejecuta el UPDATE que asigna null en el campo SupplierID para quitar la asociación, para luego si poder eliminar el registro del proveedor sin conflicto </p>
<p><a href="http://lh5.ggpht.com/-wx0JSZovw70/UdWzUTTG_iI/AAAAAAAABL8/U7bINmYWT2A/s1600-h/image62.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-G2v2HpA0__0/UdWzVDTW9pI/AAAAAAAABME/rpXGUETJ2NU/image_thumb41.png?imgmax=800" width="534" height="157" /></a></p>
<p>la validación en el test confirma que el proveedor ya no existe, al recuperarlo se obtiene un null, no así con los productos que siguen existiendo </p>
<p><a href="http://lh3.ggpht.com/-XLTKmnxL-qA/UdWzV1EKAYI/AAAAAAAABMM/Cgu2E1ed5tE/s1600-h/image63.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-iPgQu9Lktbg/UdWzWycVoEI/AAAAAAAABMU/UgABq2dlQ8I/image_thumb42.png?imgmax=800" width="543" height="263" /></a></p>
<p> </p>
<p> </p>
<p><strong>2 - Asociar mediante la instancia de la entidad </strong>
<hr /></p>
<p>Si tenemos la instancia de una entidad y la asignamos a la propiedad de otro para relacionarla, al persistir no solo creara la entidad principal sino que creara una nuevo registro de la entidad que asignamos.</p>
<p>Si tenemos un código que hiciera esto:</p>
<p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:926c0ed7-be79-4f43-bf11-7a9c6f2ba690" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
SupplierRepository repoSupplier = new SupplierRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea un proveedor
Supplier supplierNew = new Supplier()
{
CompanyName = "Company 1",
};
repoSupplier.Create(supplierNew);
//se crea el producto relacionado con la categoria
Product productNew = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
Category = categoryNew,
Supplier = supplierNew
};
repoProduct.Create(productNew);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
</p>
<p>Debe observarse particularmente como se crean las entidades satélite y luego se asignan estas a las propiedades Category y Supplier de la entidad Product, hacer esto resultara en las consultas siguientes:</p>
<p><a href="http://lh5.ggpht.com/-xK2DJG-dsxI/UdWzX44MeYI/AAAAAAAABMc/yPkWWHUBxxw/s1600-h/image131.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-oI9Uwq3BVy0/UdWzZHeYT0I/AAAAAAAABMk/fLkRMaY4OQo/image_thumb8.png?imgmax=800" width="721" height="326" /></a></p>
<p>como se observa en una primer acción se crean las entidades Category y Supplier, pero al crear el Product nuevamente se vuelven a insertar un nuevo Category y Supplier que termina relacionado </p>
<p>Esto sucede porque en el código del repositorio cuando adjuntamos la entidad Product al contexto indicamos que es una nueva entidad, pero las instancias de los objetos relacionados no le decimos nada, por lo que EF deduce que estas entidades también deben crearse.</p>
<p>La solución rápida a este problema se consigue haciendo uso de las propiedades que definen el Foreign Key y no utilizar las propiedades definidas como clase, o sea utilizar:</p>
<p><a href="http://lh4.ggpht.com/-VTJhNguNX8U/UdWzZ0EfeWI/AAAAAAAABMs/a8QM1-2roxk/s1600-h/image191.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-eVpgyEw2yDg/UdWzajsc9HI/AAAAAAAABM0/FLBERLYqQac/image_thumb121.png?imgmax=800" width="320" height="188" /></a></p>
<p>Pero vamos a ver que puede haber otra alternativa si adaptamos la clase del repositorio pudiendo indicar que propiedades deben cambiar de estado.</p>
<p> </p>
<p><strong>2.1- Marcar entidades relacionadas con Unchanged </strong></p>
<hr />
<p>Que sucede si aun así queremos asignar las instancias, existe un solución pero requiere que desde el repositorio asignemos el “State” del contexto como “Unchanged”.</p>
<p>Para lograrlo vamos a necesitar que Reflection nos ayude a recuperar el objeto que se asigna a la propiedad, desde el repositorio implementaremos.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:48c425ae-af5b-4024-827b-3744a725f095" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public virtual void Create(T entity, List<Expression<Func<T, object>>> unchangeProp)
{
// se obtiene la lista de propiedades que deben marcarse con el estado Unchanged
List<string> unchangelist = unchangeProp.Select(x => ((MemberExpression)x.Body).Member.Name).ToList();
using (NorthWindContext context = new NorthWindContext())
{
context.Set<T>().Add(entity);
if (unchangeProp != null)
{
// se toma la instancia del objeto que esta asignada a la propiedad
// y se asigna el estodo Unchanged
foreach (string property in unchangelist)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(property);
var value = propertyInfo.GetValue(entity, null);
context.Entry(value).State = EntityState.Unchanged;
}
}
context.SaveChanges();
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El parámetro “unchangeProp” nos permite definir las propiedades asignadas y no queremos que la entidad sea creadas nuevamente.</p>
<p>Desde el código del test tendremos que definir que propiedades participaran</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2590761e-a12b-4af4-acc3-6afd6f91d5f9" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Create_Unchange_CategoryAndSupplier_Product()
{
ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
SupplierRepository repoSupplier = new SupplierRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea un proveedor
Supplier supplierNew = new Supplier()
{
CompanyName = "Company 1",
};
repoSupplier.Create(supplierNew);
//se crea el producto relacionado con la categoria
Product productNew = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
Category = categoryNew,
Supplier = supplierNew
};
repoProduct.Create(productNew,
new List<Expression<Func<Product, object>>>() { x => x.Category, x => x.Supplier });
//se recupea el producto con la categoria y proveedor asociado
var productSelected = repoProduct.Single(x => x.ProductID == productNew.ProductID,
new List<Expression<Func<Product, object>>>() { x => x.Category, x => x.Supplier });
Assert.IsNotNull(productSelected.Category);
Assert.AreEqual(productSelected.Category.CategoryID, productNew.CategoryID);
Assert.IsNotNull(productSelected.Supplier);
Assert.AreEqual(productSelected.Supplier.SupplierID, productNew.SupplierID);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>de todo el test el punto importante donde hay que enfocarse seria:</p>
<p><a href="http://lh5.ggpht.com/-Eef27xkY67E/UdWzbPX3jkI/AAAAAAAABM8/rCvgvENm5WI/s1600-h/image25.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-PsCg7kOgiys/UdWzb3zegqI/AAAAAAAABNE/74dz2zI3Ds8/image_thumb16.png?imgmax=800" width="618" height="197" /></a></p>
<p>allí es donde se especifica las propiedades que deben asignarse como “Unchanged”</p>
<p>Al ejecutar el test veremos que solo se crea el INSERT del Product y no de las entidades que se se asignaron como sucedía anteriormente.</p>
<p><a href="http://lh4.ggpht.com/-D8FNCwh95_Y/UdWzc0EPrII/AAAAAAAABNM/RxPq_enLrHs/s1600-h/image31.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/--eTeJ570jK0/UdWzdk2uvHI/AAAAAAAABNU/ibYeeafKyks/image_thumb201.png?imgmax=800" width="674" height="239" /></a></p>
<p>Ahora si podemos trabajas con las propiedades de instancia para crear las relaciones sin el problema que se vuelvan a crear nuevamente cuando las entidades ya existe.</p>
<p> </p>
<p><strong>Código </strong></p>
<hr />
<p>El código se encuentra en la primer parte del artículo.</p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com16tag:blogger.com,1999:blog-7361892840793499128.post-84714315623773222122013-07-04T07:44:00.001-07:002013-07-04T07:44:39.572-07:00[Entity Framework][Code First] Asociación uno a muchos (2/3)<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>En esta segunda parte del artículo se ejecutaran varios Test que permitirán analizaran las consultas que Entity Framework generara contra la base de datos.</p> <p>La primer parte del artículo define las entidades que aquí se validaran con los test.</p> <p><a href="http://ltuttini.blogspot.com.ar/2013/07/entity-frameworkcode-first-asociacion.html">[Entity Framework][Code First] Asociación uno a mucho (1/3)</a></p> <p> </p> <p><strong>Test – Crear Producto con categorías y proveedores existentes </strong></p> <hr /> <p>Crearemos de forma individual la entidad de categorías y proveedores para luego asociarlas al producto.</p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2dc8e5e0-e6b3-4256-81d2-74e552cad951" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetSingle_WithCategoryAndSupplier_Product()
{
ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
SupplierRepository repoSupplier = new SupplierRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea un proveedor
Supplier supplierNew = new Supplier()
{
CompanyName = "Company 1",
};
repoSupplier.Create(supplierNew);
//se crea el producto relacionado con la categoria
Product prod = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
CategoryID = categoryNew.CategoryID,
SupplierID = supplierNew.SupplierID
};
repoProduct.Create(prod);
//se recupea el producto con la categoria y proveedor asociado
var productSelected = repoProduct.Single(x => x.ProductID == prod.ProductID,
new List<Expression<Func<Product, object>>>() { x => x.Category, x=>x.Supplier });
Assert.IsNotNull(productSelected.Category);
Assert.AreEqual(productSelected.Category.CategoryID, categoryNew.CategoryID);
Assert.IsNotNull(productSelected.Supplier);
Assert.AreEqual(productSelected.Supplier.SupplierID, supplierNew.SupplierID);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Se instancias los repositorios para cada una de las entidades intervinientes y se crean las entidades de forma individual.</p>
<p>Por medio de la propiedad que representan la Foreing Key es que logramos la asociación entre las entidades. Cada operación genera en la db las siguientes operaciones de insert</p>
<p><a href="http://lh6.ggpht.com/-mJMEwT7etIY/UdWKCYRjFaI/AAAAAAAABG0/sSNJViZ0BGI/s1600-h/image_thumb312.png" target="_blank"><img title="image_thumb[31]" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image_thumb[31]" src="http://lh3.ggpht.com/-y9E4eQFl2Bg/UdWKDY0UqzI/AAAAAAAABG8/uq72x45NtfM/image_thumb31_thumb.png?imgmax=800" width="692" height="244" /></a></p>
<p>Al recuperar el producto y sus relaciones se debe especificar cuales se quiere traer en al definición del parámetro del include, esto genera la consulta:</p>
<p><a href="http://lh5.ggpht.com/-HzqiU1cH_YI/UdWKEiOV8jI/AAAAAAAABHE/copRwbHY1Mw/s1600-h/image_thumb212.png" target="_blank"><img title="image_thumb[21]" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image_thumb[21]" src="http://lh5.ggpht.com/-j0WAmC6oGf8/UdWKFl-NLMI/AAAAAAAABHM/sDe2PTOQB4w/image_thumb21_thumb.png?imgmax=800" width="693" height="196" /></a></p>
<p>en una sola operación se recupera el producto, la categoría y el proveedor optimizando el acceso a la base de datos</p>
<p> </p>
<p><strong>Test – Crear Producto con categorías y proveedores NO existentes </strong></p>
<hr />
<p>Si se necesita en un único paso crear una entidad y sus relaciones se puede definir como el siguiente test </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:ff553ec8-3ae2-455f-8840-63b15ed14d3b" class="class" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:06012d40-909e-463f-b604-eab445933306" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Create_WithCategoryAndSupplier_Product()
{
ProductRepository repoProduct = new ProductRepository();
//se crea el producto relacionado con la categoria
Product productNew = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
Category = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
},
Supplier = new Supplier()
{
CompanyName = "Company 1",
}
};
repoProduct.Create(productNew);
//se recupea el producto con la categoria y proveedor asociado
var productSelected = repoProduct.Single(x => x.ProductID == productNew.ProductID,
new List<Expression<Func<Product, object>>>() { x => x.Category, x => x.Supplier });
Assert.IsNotNull(productSelected.Category);
Assert.AreEqual(productSelected.Category.CategoryID, productNew.CategoryID);
Assert.IsNotNull(productSelected.Supplier);
Assert.AreEqual(productSelected.Supplier.SupplierID, productNew.SupplierID);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>solo se utiliza un único repositorio creando las entidades asociadas directamente en la misma acción, al ejecutar en la db se registras las operaciones de insert por cada entidad </p>
<p><a href="http://lh3.ggpht.com/-yoci8Z-ZgSE/UdWKGQyYrmI/AAAAAAAABHU/8Vsf-4W1G8E/s1600-h/image_thumb322.png" target="_blank"><img title="image_thumb[32]" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image_thumb[32]" src="http://lh5.ggpht.com/-69U6pTf6mxA/UdWKH7U0tzI/AAAAAAAABHc/FHCmejZotko/image_thumb32_thumb.png?imgmax=800" width="698" height="203" /></a></p>
<p>a diferencia del test anterior aquí las operaciones se realizan una a continuación de otra, luego de la creación de las entidades estas tendrán asignado los Id que genera la db para todas las entidades, tanto la principal como para las relacionadas</p>
<p>el select que recupera el producto no sufre cambios comparado con el test anterior</p>
<p><a href="http://lh5.ggpht.com/-SAeJQJiAvWU/UdWKIxXUx5I/AAAAAAAABHk/uL6r1YpI1LY/s1600-h/image_thumb332.png" target="_blank"><img title="image_thumb[33]" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image_thumb[33]" src="http://lh5.ggpht.com/-MF9Avp5WU_M/UdWKJ0OQ6PI/AAAAAAAABHs/qKlKSWsSz10/image_thumb33_thumb.png?imgmax=800" width="700" height="198" /></a></p>
<p> </p>
<p><strong>Test – Crear Categoría con Productos existentes </strong></p>
<hr />
<p>Crearemos el productos de forma independiente para después relacionar con la categoría.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:39ce87ea-ffae-4434-8699-7498dc078432" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetSingle_AllProducts_Category()
{
ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea el producto relacionado con la categoria
Product productNew1 = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
CategoryID = categoryNew.CategoryID
};
repoProduct.Create(productNew1);
Product productNew2 = new Product()
{
ProductName = "prod 2",
UnitPrice = 12,
Discontinued = false,
CategoryID = categoryNew.CategoryID
};
repoProduct.Create(productNew2);
var categorySelected = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID,
new List<Expression<Func<Category,object>>>() { x=>x.Products });
Assert.IsNotNull(categorySelected);
Assert.IsNotNull(categorySelected.Products);
Assert.AreEqual(categorySelected.Products.Count, 2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Sino se define el include al recuperar la categoría la propiedad “Products” devolverá null</p>
<p><a href="http://lh4.ggpht.com/-yP67bZkH9ME/UdWKKp9EIRI/AAAAAAAABH0/vYOO5IA8GrM/s1600-h/image5.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-GWYxlUTY4uM/UdWKLX1vdjI/AAAAAAAABH8/_ErNPuljrn8/image_thumb3.png?imgmax=800" width="514" height="93" /></a></p>
<p>pero si se define el “include” se podrán obtener los productos relacionados a la entidad</p>
<p><a href="http://lh4.ggpht.com/-jgEXKbnr454/UdWKMGxOstI/AAAAAAAABIE/gT1KDL5tABY/s1600-h/image18.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-5-7PPnWNggM/UdWKM5Ck4-I/AAAAAAAABIM/6brDuLAySGg/image_thumb12.png?imgmax=800" width="518" height="92" /></a></p>
<p>se ejecuta un único select para recuperar la categoría y sus productos</p>
<p><a href="http://lh4.ggpht.com/-XtwyG-5Zw8U/UdWKOlvwLfI/AAAAAAAABIU/PXe5s0uckp0/s1600-h/image12.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-_1YQiWPvG-Q/UdWKQam7pYI/AAAAAAAABIc/6vxtfpa0608/image_thumb8.png?imgmax=800" width="691" height="318" /></a></p>
<p>el “include” optimiza la consulta generada por EF para recuperar las entidades</p>
<p> </p>
<p><strong>Test – Crear Categoría con Productos NO existentes </strong></p>
<hr />
<p>Se puede crear las entidades y asociarlas en una misma acción, pero debe recordarse que se crearan registros nuevos en la tabla para la entidad principal como para las relaciones</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fe12d123-f22f-4ada-a8b7-424b0a011e29" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Create_WithProducts_Category()
{
CategoryRepository repoCategory = new CategoryRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1",
Products = new List<Product>(){
new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false
},
new Product()
{
ProductName = "prod 2",
UnitPrice = 12,
Discontinued = false
}
}
};
repoCategory.Create(categoryNew);
var categorySelected = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID,
new List<Expression<Func<Category, object>>>() { x => x.Products });
Assert.IsNotNull(categorySelected);
Assert.IsNotNull(categorySelected.Products);
Assert.AreEqual(categorySelected.Products.Count, 2);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La creación de los registros ejecutara varias instrucciones insert, luego de la creación del registro devolverá los Id generados en cada instrucción</p>
<p><a href="http://lh4.ggpht.com/-r4P7gA6uzG8/UdWKSM9GvzI/AAAAAAAABIk/vP2Mzmt6mA8/s1600-h/image24.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-bLO9-N7I-2w/UdWKTOmzQiI/AAAAAAAABIs/lTKQ1Ry3ajU/image_thumb16.png?imgmax=800" width="684" height="182" /></a></p>
<p>al recuperar al entidad principal y su relación con la colección optimizara el select para realizarlo en una única operación</p>
<p><a href="http://lh5.ggpht.com/-yT8XYlXiVtc/UdWKUbdCh-I/AAAAAAAABI0/q7JqKu64iaE/s1600-h/image30.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-E3TZLlzSkaY/UdWKVQtjCPI/AAAAAAAABI8/H7QfVQ71zgs/image_thumb20.png?imgmax=800" width="684" height="292" /></a></p>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p>El código se encuentra en la primer parte del artículo.</p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com9tag:blogger.com,1999:blog-7361892840793499128.post-35663580965137276212013-07-03T14:19:00.001-07:002013-07-07T17:21:23.976-07:00[Entity Framework][Code First] Asociación uno a muchos (1/3)<p> </p><p><strong>Introducción </strong> <hr /></p><p>Al hacer uso de entidades rara vez son simples y están aisladas, lo normal es que una entidad interactué con otras relacionándose por medio de propiedades que permiten navegar sus ítems.</p><p>Durante este artículo y los siguiente veremos como Entity Framework nos ayudara en la tarea de recuperar las asociaciones a otras entidades.</p><p>Empezaremos por la configuración simple, para luego especializar la configuración, analizaremos como afecta las opciones de lazy load al definir el repositorio. En el siguiente artículo analizaremos mediante la ejecución de Test las distintas formas de asociar entidades y como repercuten en las consultas que EF generara contra la db. En el ultimo artículo veremos como eliminar cascada y asociar las entidades mediante la asignación de la instancia en las propiedades.</p><p> </p><p><strong>Definición de las entidades </strong> <hr /></p><p>Empezaremos definiendo las entidades que formaran nuestro dominio</p><p><a href="http://lh6.ggpht.com/-5Ls40cdqryc/UdSVGi-36OI/AAAAAAAABFc/XpcVf_2KUHM/s1600-h/image5.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-x6OCyKJM2qA/UdSVJcyL0WI/AAAAAAAABFk/KQgPBMhV7f8/image_thumb3.png?imgmax=800" width="558" height="248" /></a></p><p> </p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:74892839-ca39-46b7-9e24-6941cbd20b3b" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public string QuantityPerUnit { get; set; }
public decimal? UnitPrice { get; set; }
public short? UnitsInStock { get; set; }
public short? UnitsOnOrder { get; set; }
public short? ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
public int? SupplierID { get; set; }
public virtual Supplier Supplier { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p> </p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:f414548c-50cc-4db9-87ff-7eb8c5946dbc" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p> </p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:87461327-0384-471a-9fb6-8f5949ce2c4e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Supplier
{
public int SupplierID { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p>El entidad Product tiene asociación con Categoría (obligatoria) y Supervisor (opcional).</p><p>En este caso se estas siguiendo las convenciones, por lo que además de la propiedad que define la clase de navegación, se debe definir una propiedad adicional que identifique el campo que actuara como Foreign Key.</p><p>La obligatoriedad o no de una relación se define mediante la asignación de un tipo Nullable en la propiedad mencionada anteriormente (al permitir nulos la relación será opcional).</p><p>Las entidades Category y Supplier disponen de una propiedad de tipo colección que referencia al Product, pero esta propiedad puede ser opcional si es que no se desea recuperar una de las direcciones de la asociación.</p><p> </p><p><strong>Configuración estándar </strong> <hr /></p><p>Si solo definimos el contexto sin especificar ningún otro detalle.</p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fa458f44-1a05-4e2b-82dc-d41c9933fc25" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p> </p><p>Al ejecutar los test estos generaran la base de datos con la estructura de tablas como la siguiente:</p><p><a href="http://lh4.ggpht.com/-4T4r573t3aU/UdSVKR_GAoI/AAAAAAAABFs/Dq1OoANYt3U/s1600-h/image12.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-QraihorkmOY/UdSVLaAhxmI/AAAAAAAABF0/wEMjqleJd-I/image_thumb8.png?imgmax=800" width="614" height="390" /></a></p><p>Como se puede visualizar con casi nada de especificación se consigue un modelo relacional aceptable, solo se siguieron algunas convenciones:</p><p>- Definir las propiedades de navegación entre las entidades y con una adicional que representa la Foreign Key</p><p>- Utilizando null en la propiedad Foreign Key para indicar si es opcional</p><p> </p><p><strong>Especificación con Fluent API</strong> <hr /></p><p>Si bien las convenciones nos dejan un modelo de persistencia bastante cercano a las necesidades, cuando la definiciones escapan a las normas se puede especializar mediante configuración, especialmente cuando se utiliza una base de datos existente.</p><p> </p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2a4d87b3-9a95-4e06-b744-672f180010f4" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CategoryMap());
modelBuilder.Configurations.Add(new ProductMap());
modelBuilder.Configurations.Add(new SupplierMap());
base.OnModelCreating(modelBuilder);
}
}
public class CategoryMap : EntityTypeConfiguration<Category>
{
public CategoryMap()
{
ToTable("Categories");
HasKey(c => c.CategoryID);
Property(c => c.CategoryID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(c => c.CategoryName).IsRequired().HasMaxLength(15);
Property(c => c.Description).HasColumnType("ntext");
}
}
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
ToTable("Products");
HasKey(x => x.ProductID);
Property(x => x.ProductID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.ProductName).HasMaxLength(40);
Property(x => x.QuantityPerUnit).HasMaxLength(20);
Property(x => x.UnitPrice).HasColumnType("money").IsOptional();
HasRequired(x => x.Category).WithMany(x => x.Products).HasForeignKey(x => x.CategoryID);
//HasRequired(x => x.Category).WithMany().HasForeignKey(x => x.CategoryID);
HasOptional(x => x.Supplier).WithMany(x => x.Products).HasForeignKey(x => x.SupplierID);
}
}
public class SupplierMap : EntityTypeConfiguration<Supplier>
{
public SupplierMap()
{
HasKey(x => x.SupplierID);
Property(x => x.CompanyName).HasMaxLength(40).IsRequired();
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p> </p><p>Al ejecutar alguno de los test se generar la estructura de la db:</p><p><a href="http://lh4.ggpht.com/-5vdz6KmloFA/UdSVM9oskTI/AAAAAAAABF8/w4KqlaFrRPs/s1600-h/image19.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-15YGELQ-uEg/UdSVOWJf_zI/AAAAAAAABGE/nsOIS60AVg8/image_thumb13.png?imgmax=800" width="612" height="374" /></a></p><p>Si bien a nivel de base de datos las relaciones no cambiaron desde código se puedo definir el código que detalla el modelo de persistencia que queremos.</p><p>Para definir la relación obligatoria con la entidad Categoría se utiliza la línea:</p><p><em>HasRequired(x => x.Category).WithMany(x => x.Products).HasForeignKey(x => x.CategoryID);</em></p><p>en este caso se define que propiedades intervienen en la relación, por supuesto si se hace uso de las convenciones esta de mas esta declaración, pero si las propiedades tienen nombres distintos definir esta línea es clave para que funcione la asociación.</p><p>La definición de la entidad proveedor como opcional se usa la línea:</p><p><em>HasOptional(x => x.Supplier).WithMany(x => x.Products).HasForeignKey(x => x.SupplierID);</em></p><p>solo cambia el uso de HasOptional(), aunque sigue siendo necesario definir la propiedad Foreign Key del tipo Nullable.</p><p> </p><p><strong>Asociación en una única dirección </strong> <hr /></p><p>Anteriormente comente que la propiedad que permite navegar la colección de productos no es obligatoria, pudiendo definirse de esta forma:</p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:27f77358-d89e-49ce-999c-5b1561f8a89e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p>Nota: se quito la propiedad que define el ICollection<> hacia los Productos</p><p>Pero si se dejara las propiedades en la entidad Product que relacione con las entidades simples.</p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:cfb4097e-97a1-46b4-8824-daaa036f992e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Product
{
//resto de las propiedades
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
public int? SupplierID { get; set; }
public virtual Supplier Supplier { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p>la configuración cambiaria a:</p><p><em>HasRequired(x => x.Category).WithMany().HasForeignKey(x => x.CategoryID);</em></p><p>solo se especifica la propiedad en una única dirección hacia la entidad simple y se quitan las colecciones.</p><p> </p><p><strong>Repository y Lazy Load </strong> <hr /></p><p>Un punto que encontré durante la definición del repositorio se relaciona con el lazy load de las propiedades que asocia las entidades.</p><p>Si ejecutamos un test como ser</p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:20321ad7-dbc0-4969-a945-48281554caf9" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void GetSingle_IncludeCategory_Product()
{
ProductRepository repoProduct = new ProductRepository();
CategoryRepository repoCategory = new CategoryRepository();
SupplierRepository repoSupplier = new SupplierRepository();
//se crea la categoria
Category categoryNew = new Category()
{
CategoryName = "category1",
Description = "desc category 1"
};
repoCategory.Create(categoryNew);
//se crea un proveedor
Supplier supplierNew = new Supplier()
{
CompanyName = "Company 1",
};
repoSupplier.Create(supplierNew);
//se crea el producto relacionado con la categoria
Product productNew = new Product()
{
ProductName = "prod 1",
UnitPrice = 10,
Discontinued = false,
CategoryID = categoryNew.CategoryID,
SupplierID = supplierNew.SupplierID
};
repoProduct.Create(productNew);
//se recupea el producto con la categoria asociada
var productSelected = repoProduct.Single(x => x.ProductID == productNew.ProductID,
new List<Expression<Func<Product, object>>>() { x => x.Category });
Assert.IsNotNull(productSelected.Category);
Assert.AreEqual(productSelected.Category.CategoryID, categoryNew.CategoryID);
Assert.AreEqual(productSelected.CategoryID, categoryNew.CategoryID);
Assert.AreEqual(productSelected.SupplierID, supplierNew.SupplierID);
Assert.IsNull(productSelected.Supplier);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><p>El cual crea un producto con categoría y proveedor relacionado, pero luego de crear la entidad se recuperar definiendo en el “include” solo la categoría, por estar estar habilitado el lazy load se obtendrá el siguiente mensaje:</p><p> </p><p><a href="http://lh3.ggpht.com/-SKMwCxil9L4/UdSVPTHBk9I/AAAAAAAABGM/a31vl_7b4ps/s1600-h/SNAGHTML7b064e648.png" target="_blank"><img title="SNAGHTML7b064e64" style="display: inline" alt="SNAGHTML7b064e64" src="http://lh3.ggpht.com/-C776CMWu4fg/UdSVQa8hXhI/AAAAAAAABGU/gHnXb7Cqm2M/SNAGHTML7b064e64_thumb5.png?imgmax=800" width="613" height="164" /></a></p><p>Este error se produce porque se crea un proxy que permite la relación de forma desatendida, pero al estar la entidad por fuera del contexto entonces falla.</p><p>Por esta razón se definieron de dos líneas en el constructor de la clase del contexto:</p><div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:f71d982e-bd24-4da7-98ef-8d4da30c68ca" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div><br />
Al deshabilitando lazy load las propiedades relacionadas que no se especifican en el “include” no recuperan las instancias, por lo tanto estarán en null. <p>Ahora si se comporta como se espera</p><p><a href="http://lh5.ggpht.com/-D8eT-pKGAcY/UdSVRBB02jI/AAAAAAAABGc/OwgRJTgCw6w/s1600-h/SNAGHTML7bae9e996.png" target="_blank"><img title="SNAGHTML7bae9e99" style="display: inline" alt="SNAGHTML7bae9e99" src="http://lh3.ggpht.com/-trpkMLujQDM/UdSVSLb-aqI/AAAAAAAABGg/0PFa_FsywQo/SNAGHTML7bae9e99_thumb3.png?imgmax=800" width="610" height="170" /></a></p><p> </p><p><strong>Documentación de referencia </strong> <hr /></p><p><a href="http://msdn.microsoft.com/es-ES/data/jj679962" target="_blank">Code First Conventions</a></p><p><a href="http://msdn.microsoft.com/es-es/data/jj591620" target="_blank">Configuring Relationships with the Fluent API</a></p><p><a href="http://msdn.microsoft.com/en-us/data/jj592886.aspx" target="_blank">Working with Proxies</a></p><p> </p><p><strong>Código </strong> <hr /></p><p>Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF.</p><table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr> <td valign="top" width="200">[C#] <br />
<iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21381&authkey=ABZSckVnjeMgKEA" frameborder="0" width="98" scrolling="no"></iframe></td> <td valign="top" width="200"> </td> </tr>
</tbody></table>Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com6tag:blogger.com,1999:blog-7361892840793499128.post-45584999264263365922013-06-30T11:39:00.000-07:002013-07-01T13:01:26.179-07:00[Entity Framework][Code First] Complex Type<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>Cuando se modelan entidades suele plantearse la necesidad de definir propiedades que se agrupen para definir un tipo de dato, ejemplo de estos podrían ser los datos de contacto, direcciones, etc</p> <p>Estos tipos de dato no tienen una entidad por si mismos, o sea no tienen un id o código que los identifique, sino que son parte de otra entidad.</p> <p>La idea es poder representar algo como lo siguiente</p> <p><a href="http://lh4.ggpht.com/-Kbxpf-NoXgg/UdHf0HdKdNI/AAAAAAAABCY/8qLVvVOy1eI/s1600-h/image7.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-F78mpPGXIYs/UdHf1slQE7I/AAAAAAAABCg/2v1gbln06Mo/image_thumb5.png?imgmax=800" width="559" height="369" /></a></p> <p>Las entidades de proveedores y empleados se asocian a otras que permiten agrupar propiedades bajo un mismo concepto.</p> <p>Pero a nivel de persistencia la vista es bastante diferente</p> <p><a href="http://lh3.ggpht.com/-zjxtrPEfE6U/UdHf2oPbgjI/AAAAAAAABCo/xJ5B5u3223E/s1600-h/image13.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-jplRuSARRwA/UdHf3Z5ny2I/AAAAAAAABCw/jWNfLDleSMg/image_thumb9.png?imgmax=800" width="637" height="303" /></a></p> <p>Los campos que se agrupan en entidades como ser Localidad o Contacto ahora son campos individuales en cada tabla.</p> <p>Existen varias formas de definir este tipo de persistencia en Entity Framework según se la quiere utilizar para una sola entidad o compartir entre varias.</p> <p> </p> <p><strong>Definición de las entidades </strong></p> <hr /> <p>Tanto la entidad empleado como el proveedor defienden un grupo de propiedades que representa la localización, pero el proveedor además define un grupo adicional denominado contacto</p> <p>La definición del proveedor se realiza en <em>Supplier.cs</em></p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:7b820f46-cb4d-4441-bcd4-e96abfe5d597" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Supplier
{
public Supplier()
{
this.Localization = new Localization();
this.Contact = new Contact();
}
public int Codigo { get; set; }
public string CompanyName { get; set; }
public Localization Localization { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public string HomePage { get; set; }
public bool HasValue
{
get
{
return this.ContactName != null
|| this.ContactTitle != null
|| this.Phone != null
|| this.Fax != null
|| this.HomePage != null;
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La definición de empleado se realiza en <em>Employee.cs</em></p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:cf461dd4-f181-4268-9cc3-61499874a2da" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Employee
{
public Employee()
{
this.Localization = new Localization();
}
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public Localization Localization { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Y ambos hacen uso de la clase que define la localización, definida en <em>LocationComplexType.cs</em> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:37d908d9-4093-441e-974f-d06d516b0ffa" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Localization
{
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public bool HasValue
{
get
{
return this.Address != null
|| this.City != null
|| this.Region != null
|| this.PostalCode != null
|| this.Country != null;
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Toda esta separación se realiza para demostrar que hay varias formas forma de separar la clase, se puede realizar físicamente en un .cs o puede definirse junto a la clase que la utiliza.</p>
<p> </p>
<p><strong>Definición básica del modelo </strong></p>
<hr />
<p>Si solo definimos las entidades sin ningún otro tipo de especificación. </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2dfdac97-9452-4f03-ac5d-1ca6dfdb9d77" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>creara los siguiente estructura de datos</p>
<p><a href="http://lh3.ggpht.com/-cPD0tRRq9jA/UdHf4II97HI/AAAAAAAABC4/J0jyQna78BA/s1600-h/image20.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-0si2Qm78tyY/UdHf4_TXf3I/AAAAAAAABDA/fELAI8rRqmo/image_thumb14%25255B1%25255D.png?imgmax=800" width="673" height="335" /></a></p>
<p>Aplicara la definición por convención para los campos a los cuales les agregara un prefijo según el tipo complejo donde estén definida la propiedades.</p>
<p>Sin definir prácticamente nada ya obtenemos un modelo de datos en donde EF aplica las convenciones, pero puede que esto no nos guste por lo que a continuación vamos a ver como personalizar la persistencia de la entidad.</p>
<p> </p>
<p><strong>Definir ComplexType para una sola entidad </strong></p>
<hr />
<p>Analicemos la entidad Supplier, en la cual se puede especificar la definición del tipo complejo directamente en la entidad, es por eso que se utiliza:</p>
<p><em>Property(x => x.Contact.ContactName)…</em></p>
<p>Se accede a la propiedad que define la clase compleja y se mapea cada una de las propiedad.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:5546884d-44e1-4a65-b3de-b3f028341e1e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class SupplierMap : EntityTypeConfiguration<Supplier>
{
public SupplierMap()
{
HasKey(x => x.SupplierID);
Property(x => x.CompanyName).HasMaxLength(40).IsRequired();
Property(x => x.Contact.ContactName).HasColumnName("ContactName").HasMaxLength(30);
Property(x => x.Contact.ContactTitle).HasColumnName("ContactTitle").HasMaxLength(30);
Property(x => x.Contact.Phone).HasColumnName("Phone").HasMaxLength(24);
Property(x => x.Contact.Fax).HasColumnName("Fax").HasMaxLength(24);
Property(x => x.Contact.HomePage).HasColumnName("HomePage").HasColumnType("ntext");
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:27ef07b0-15b9-44d1-a3d7-36194091f592" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SupplierMap());
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>la línea:</p>
<p><em>modelBuilder.Configurations.Add(new SupplierMap());</em></p>
<p>define la clase que tiene las especificaciones del mapping de la entidad</p>
<p>Estas modificaciones cambian el aspecto de la tabla generada en base al modelo</p>
<p><a href="http://lh3.ggpht.com/-uYdY4sQihW8/UdHf5hePWmI/AAAAAAAABDI/rGpPpW8U1fo/s1600-h/image40.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-wFi9csuZ8Zw/UdHf6vrEhqI/AAAAAAAABDQ/qnUp2VO8hcU/image_thumb28.png?imgmax=800" width="406" height="386" /></a></p>
<p> </p>
<p><strong>Definir ComplexType compartido entre entidades </strong></p>
<hr />
<p>Tanto la entidad Supplier como Employee  definen un tipo complejo en común, definido en la clase Localization.</p>
<p>El mapping de esta entidad puede realizarse de dos formas:</p>
<ul>
<li>Se puede definir mediante ComplexTypeConfiguration<>  en el OnModelCreating()</li>
</ul>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:7f884b6c-d271-4c86-9882-fa36033d8e91" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SupplierMap());
ComplexTypeConfiguration<Localization> complexLocalizacion = modelBuilder.ComplexType<Localization>();
complexLocalizacion.Property(x => x.Address).HasColumnName("Address").HasMaxLength(60);
complexLocalizacion.Property(x => x.City).HasColumnName("City").HasMaxLength(15);
complexLocalizacion.Property(x => x.Region).HasColumnName("Region").HasMaxLength(15);
complexLocalizacion.Property(x => x.PostalCode).HasColumnName("PostalCode").HasMaxLength(10);
complexLocalizacion.Property(x => x.Country).HasColumnName("Country").HasMaxLength(15);
modelBuilder.Configurations.Add(new EmployeeMap());
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<ul>
<li>Definir una clase de mapping que especifique la configuración </li>
</ul>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:6282b30a-6dad-4a00-b0a3-ea7548eaa15e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SupplierMap());
modelBuilder.Configurations.Add(new LocationComplexMap());
modelBuilder.Configurations.Add(new EmployeeMap());
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:42439e99-e187-428a-b0bb-e7a8d1abc326" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class LocationComplexMap : EntityTypeConfiguration<Localization>
{
public LocationComplexMap()
{
Property(x => x.Address).HasColumnName("Address").HasMaxLength(60);
Property(x => x.City).HasColumnName("City").HasMaxLength(15);
Property(x => x.Region).HasColumnName("Region").HasMaxLength(15);
Property(x => x.PostalCode).HasColumnName("PostalCode").HasMaxLength(10);
Property(x => x.Country).HasColumnName("Country").HasMaxLength(15);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Por medio de la línea:</p>
<p><em>modelBuilder.Configurations.Add(new LocationComplexMap());</em></p>
<p>se asocia la clase de mapping con la definición del contexto</p>
<p>La ejecución del Test generara en la base de datos las tablas con al estructura y tipos de datos que buscamos:</p>
<p><a href="http://lh3.ggpht.com/-6TCh9Qmbt4w/UdHf7JqVemI/AAAAAAAABDY/KWvAINmT5tA/s1600-h/image34.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-Jc1h2Uf22hc/UdHf73j3XgI/AAAAAAAABDg/G7HIcIIkgoI/image_thumb24.png?imgmax=800" width="659" height="326" /></a></p>
<p> </p>
<p><strong>Test - Recuperar todos los proveedores </strong></p>
<hr />
<p>Los diferentes test que confeccionemos nos ayudara a validar la definición del mapping de cada entidad</p>
<p>Recuperamos la lista de todos los proveedores</p>
<p><a href="http://lh6.ggpht.com/-HsbYUMA_ZFA/UdHf9FPkwfI/AAAAAAAABDo/EHs43EJx36s/s1600-h/SNAGHTML52fcba0f7.png" target="_blank"><img title="SNAGHTML52fcba0f" style="display: inline" alt="SNAGHTML52fcba0f" src="http://lh5.ggpht.com/-i42NHyiP9K8/UdHf-f1aXxI/AAAAAAAABDw/ygG5y0ttbhA/SNAGHTML52fcba0f_thumb4.png?imgmax=800" width="685" height="344" /></a></p>
<p>La ejecución del test inserta un nuevo proveedor</p>
<p><a href="http://lh3.ggpht.com/-upSi9F1tlbg/UdHf_HJF6MI/AAAAAAAABD4/RBMsLk63RuE/s1600-h/image13%25255B1%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-jBQT1kMw2Ek/UdHf_11Ov9I/AAAAAAAABEA/0XsO63eU4yM/image_thumb8.png?imgmax=800" width="649" height="116" /></a></p>
<p>Y luego lo recupera</p>
<p><a href="http://lh4.ggpht.com/-U_XMbOVHFaE/UdHgAJ9eaoI/AAAAAAAABEI/f7cSzPPqFnQ/s1600-h/image15.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-8OhSS0gS0dU/UdHgA4sgEII/AAAAAAAABEQ/6WMADFs82Vo/image_thumb10.png?imgmax=800" width="534" height="231" /></a></p>
<p>Se puede ver a simple fácilmente como se incluye en las queries los campos que forman las entidad complejas.</p>
<p> </p>
<p><strong>Test - Recuperar un solo empleado </strong>
<hr /></p>
<p>En el test del empleado se crea una nueva entidad empleado, esto luego se recupera para validar que los campos son correctamente mapeados contra la tabla.</p>
<p> </p>
<p><a href="http://lh4.ggpht.com/-oVNazXcAFwo/UdHgBTNVgII/AAAAAAAABEY/FXDvkhv6CCU/s1600-h/image46.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh5.ggpht.com/-1Vel_5m9wYE/UdHgCKkXyKI/AAAAAAAABEg/nXNJnppkzzU/image_thumb32.png?imgmax=800" width="453" height="369" /></a></p>
<p><a href="http://lh3.ggpht.com/-FWVRAnoJVis/UdHgC8y67wI/AAAAAAAABEo/6mVm9TFPwOU/s1600-h/image%25255B7%25255D.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-6UZAQBtnPMA/UdHgDm4CY3I/AAAAAAAABEw/eXN8e0vuQcU/image_thumb%25255B4%25255D.png?imgmax=800" width="645" height="106" /></a></p>
<p>Al igual que en el test anterior la consulta filtra por el id de la entidad que se quiere recuperar incluyendo los campos que conforman la entidad mas los que definen los tipos complejos.</p>
<p><a href="http://lh5.ggpht.com/-k9JMxBEO9eE/UdHgD3SsewI/AAAAAAAABE4/c3mwJAQ9q00/s1600-h/image21.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-73jnO9C9jGQ/UdHgE6tKHXI/AAAAAAAABFA/Hg8uy8dwnKQ/image_thumb14.png?imgmax=800" width="546" height="163" /></a></p>
<p> </p>
<p><strong>Código </strong></p>
<hr />
<p>Se utiliza Visual Studio 2012, la base de datos es creada por el mismo Entity Framework cuando se ejecutan los test</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21379&authkey=AGTno3KzZ6kn_eA" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com6tag:blogger.com,1999:blog-7361892840793499128.post-51568688358329461072013-06-29T11:25:00.000-07:002013-07-01T11:35:21.665-07:00[Entity Framework][Code First] Crear entidad simple<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Este artículo sea el inicio de varios donde iremos analizando paso a paso las diferentes alternativas que Entity Framework nos brinda en su modalidad Code First.</p> <p>Partiremos de ejemplos simples hasta analizar implementaciones mas complejas, evaluando como se generan las consultas que impactaran en la base de datos.</p> <p>Haremos uso de la estructura planteada por la db NorthWind, agregándole algunas modificaciones para poder estudiar algunos casos no contemplados en el modelo original, como ser el caso de la herencia.</p> <p>Comenzaremos armando la estructura del proyecto, el cual no contara con ninguna interfaz grafica ya que haremos uso de proyectos de Test para aplicar validar la lógica que permite recuperar los datos.</p> <p>También armaremos el código haciendo uso del concepto de Repository dejando encapsulado la funcionalidad de Entity Framework a un solo proyecto.</p> <p> </p> <p><strong>Incluir librería Proyecto </strong> <hr /></p> <p>La estructura esta formado por 3 proyecto:</p> <ul> <li><strong>Test</strong>, el cual permitirá probar la funcionalidad y evaluar el correcto desarrollado</li> <li><strong>DataAccess</strong>, donde se definen las clases repository, así como también el contexto que requiere Code First para mapear las entidades </li> <li><strong>Entities</strong>, define las clases que representan las entidades de negocio </li> </ul> <p><a href="http://lh6.ggpht.com/-NeRUcLczhGs/UdHLS_JR-dI/AAAAAAAAA-w/qtQ2R0ArIOY/s1600-h/image4.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-4qcsmappM2A/UdHLUyKFJkI/AAAAAAAAA-4/n-lYAgPi1zw/image_thumb2.png?imgmax=800" width="240" height="152" /></a></p> <p>en la capa de DataAccess es donde necesitaremos referenciar la librería de Entity Framework, para ello haremos uso de NuGet</p> <p><a href="http://lh3.ggpht.com/-ts6uC_tUNCg/UdHLWITxg5I/AAAAAAAAA_A/sJWUlOoLoUI/s1600-h/SNAGHTML467f8686.png" target="_blank"><img title="SNAGHTML467f868" style="display: inline" alt="SNAGHTML467f868" src="http://lh5.ggpht.com/-pFZw9hSnsl4/UdHLYGYTgLI/AAAAAAAAA_I/tO126x1hGAY/SNAGHTML467f868_thumb3.png?imgmax=800" width="434" height="318" /></a></p> <p> </p> <p><a href="http://lh3.ggpht.com/-QqZaCHLsXc0/UdHLaBW5HPI/AAAAAAAAA_Q/LEO8T1MEqXs/s1600-h/SNAGHTML65f6037.png" target="_blank"><img title="SNAGHTML65f603" style="display: inline" alt="SNAGHTML65f603" src="http://lh5.ggpht.com/-rOjrNGtpZ4k/UdHLckmBWeI/AAAAAAAAA_Y/ZHaKsh4B8eg/SNAGHTML65f603_thumb4.png?imgmax=800" width="491" height="336" /></a></p> <p>Una vez instalada la librería podremos verla como referencia en el proyecto</p> <p><a href="http://lh6.ggpht.com/-iJMn5YTCVaQ/UdHLd0F19FI/AAAAAAAAA_g/h2hR6pZj94Y/s1600-h/SNAGHTML4690fdf6.png" target="_blank"><img title="SNAGHTML4690fdf" style="display: inline" alt="SNAGHTML4690fdf" src="http://lh4.ggpht.com/-hNFimUKfOmU/UdHLfcKNgcI/AAAAAAAAA_o/K-87yYnUV34/SNAGHTML4690fdf_thumb3.png?imgmax=800" width="301" height="369" /></a></p> <p> </p> <p><strong>Definir conexión a la Base de Datos </strong> <hr /></p> <p>Para poder usar EF debemos definir la cadena de conexión en un archivo App.config o Web.config según sea el tipo de proyecto que se este usando, en este caso al ejecutar desde un Test será un App.config con el siguiente contenido:</p> <div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:b26c3ac2-f0a9-4c0f-98c4-c834eed3d9e3" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: xml; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "><?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="NorthwindDb"
connectionString="Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>El name de la key de la conexión se utilizara en la definición de la clase del contexto.</p>
<p><a href="http://lh4.ggpht.com/-TzpUZKCj87w/UdHLhLNmtUI/AAAAAAAAA_w/f1j6nVxs6rY/s1600-h/SNAGHTML341d896c7.png" target="_blank"><img title="SNAGHTML341d896c" style="display: inline" alt="SNAGHTML341d896c" src="http://lh5.ggpht.com/-k8FFzfCwAUQ/UdHLircFnGI/AAAAAAAAA_4/xuXxEBO9uxY/SNAGHTML341d896c_thumb4.png?imgmax=800" width="628" height="219" /></a></p>
<p>En este caso vamos a utilizar el servicio de Sql Server y no un attach dinámico porque dejaremos que el modelo definido en EF genere la base de datos, de esta forma se podrá analizar la estructura de tablas resultante. </p>
<p>Si la base de datos existe previamente (salvo que se lo indique lo contrario) usara esa db, sino la creara basándose en la estructura definida en el modelo.</p>
<p> </p>
<p><strong>Definición del contexto </strong>
<hr /></p>
<p>El proyecto encargado de definir la persistencia será: <em>NorthWind.DataAccess</em></p>
<p>Empezaremos creando la clase de contexto: <em>NorthWindDataContext</em></p>
<p><em></em></p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:99b656ca-0c9d-4204-8c89-e98954bc22a0" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La clase hereda de DbContext, y define en el constructor la key del archivo de configuración que usara para tomar la conexión a la base de datos. </p>
<p>Por cada entidad que necesitemos mapear con una tabla se creara una propiedad cuyo tipo será DbSet<></p>
<p>Dejamos preparado el contexto con la definición del <em>OnModelCreating</em> donde podremos personalizar el mapeo de la entidad, esto lo veremos mas adelante.</p>
<p> </p>
<p><strong>Definición del Repositorio Genérico </strong>
<hr /></p>
<p>La clase del repositorio permitirá definir las acciones sobre la persistencia que serán comunes para todas las entidades.</p>
<p>Comenzaremos definiendo la interfaz del repositorio:</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:f9aeaa28-8490-4eb2-b9ab-ef5b2495b7aa" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">interface IRepository<T> where T:class
{
List<T> GetAll();
List<T> GetAll(List<Expression<Func<T, object>>> includes);
T Single(Expression<Func<T, bool>> predicate);
T Single(Expression<Func<T, bool>> predicate, List<Expression<Func<T, object>>> includes);
List<T> Filter(Expression<Func<T, bool>> predicate);
List<T> Filter(Expression<Func<T, bool>> predicate, List<Expression<Func<T, object>>> includes);
void Create(T entity);
void Update(T entity);
void Delete(T entity);
void Delete(Expression<Func<T, bool>> predicate);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La implementación base </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:b2a052ba-a6c5-4c86-b5c5-ddeeaaea9afd" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public abstract class BaseRepository<T> : IRepository<T> where T:class
{
public List<T> GetAll()
{
using (NorthWindContext context = new NorthWindContext())
{
return (List<T>)context.Set<T>().ToList();
}
}
public List<T> GetAll(List<Expression<Func<T, object>>> includes)
{
List<string> includelist = new List<string>();
foreach (var item in includes)
{
MemberExpression body = item.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
includelist.Add(body.Member.Name);
}
using (NorthWindContext context = new NorthWindContext())
{
DbQuery<T> query = context.Set<T>();
includelist.ForEach(x => query = query.Include(x));
return (List<T>)query.ToList();
}
}
public T Single(Expression<Func<T, bool>> predicate)
{
using (NorthWindContext context = new NorthWindContext())
{
return context.Set<T>().FirstOrDefault(predicate);
}
}
public T Single(Expression<Func<T, bool>> predicate, List<Expression<Func<T, object>>> includes)
{
List<string> includelist = new List<string>();
foreach (var item in includes)
{
MemberExpression body = item.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
includelist.Add(body.Member.Name);
}
using (NorthWindContext context = new NorthWindContext())
{
DbQuery<T> query = context.Set<T>();
includelist.ForEach(x => query = query.Include(x));
return query.FirstOrDefault(predicate);
}
}
public List<T> Filter(Expression<Func<T, bool>> predicate)
{
using (NorthWindContext context = new NorthWindContext())
{
return (List<T>)context.Set<T>().Where(predicate).ToList();
}
}
public List<T> Filter(Expression<Func<T, bool>> predicate, List<Expression<Func<T, object>>> includes)
{
List<string> includelist = new List<string>();
foreach (var item in includes)
{
MemberExpression body = item.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
includelist.Add(body.Member.Name);
}
using (NorthWindContext context = new NorthWindContext())
{
DbQuery<T> query = context.Set<T>();
includelist.ForEach(x => query = query.Include(x));
return (List<T>)query.Where(predicate).ToList();
}
}
public void Create(T entity)
{
using (NorthWindContext context = new NorthWindContext())
{
context.Set<T>().Add(entity);
context.SaveChanges();
}
}
public void Update(T entity)
{
using (NorthWindContext context = new NorthWindContext())
{
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}
}
public void Delete(T entity)
{
using (NorthWindContext context = new NorthWindContext())
{
context.Entry(entity).State = EntityState.Deleted;
context.SaveChanges();
}
}
public void Delete(Expression<Func<T, bool>> predicate)
{
using (NorthWindContext context = new NorthWindContext())
{
var entities = context.Set<T>().Where(predicate).ToList();
entities.ForEach(x => context.Entry(x).State = EntityState.Deleted);
context.SaveChanges();
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Como se puede observar en este caso el repositorio define un contexto fijo para no complicar la implementación, pero se podría hacer uso de algún framework de IoC para inyectar el contexto ha utilizar.</p>
<p> </p>
<p><strong>Creación de una entidad simple </strong>
<hr /></p>
<p>Para empezar vamos a hacer uso de una entidad muy simple, definiéndola en el proyecto: <em>NorthWind.Entities</em></p>
<p>Se trata de una entidad que define categorías</p>
<p><a href="http://lh3.ggpht.com/-8NyMbJOAy8I/UdHLjHHJUyI/AAAAAAAABAA/8PMr17_29QY/s1600-h/image9.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-QkxQgsO9DU0/UdHLkQYIMNI/AAAAAAAABAI/W3md_0sKd78/image_thumb5.png?imgmax=800" width="190" height="186" /></a></p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:be528d1a-bb22-46b2-8aeb-218ae4605a38" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><strong>Definición del Repositorio especifico para al entidad </strong>
<hr /></p>
<p>Es necesario implementar un repositorio para la entidad haciendo uso del base. </p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:c29c7dab-6607-40f4-b8ec-e330763fccf7" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public interface ICategoryRepository
{
Category GetById(int categoryID);
}
public class CategoryRepository : BaseRepository<Category>, ICategoryRepository
{
public Category GetById(int categoryID)
{
using (NorthWindContext context = new NorthWindContext())
{
return context.Set<Category>().FirstOrDefault(x => x.CategoryID == categoryID);
}
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>En esta entidad se puede especializar funcionalidad que se necesites puntualmente, en el ejemplo se implemento un método que devuelve la entidad según su id, el método es definido no solo en la clase concreta del repositorio, sino que se especifica una interfaz <em>ICategoryRepository</em> para permitir cambiar la implementación en caso de ser necesario (o si se utiliza algún IoC).</p>
<p>Por supuesto siempre existen alternativas, el método <em>GetById()</em> podría haberse omitido ya que el mismo dato podría haberse recuperado mediante la el método Single() que define el repositorio base, utilizando:</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d888d914-cbda-4040-bbe5-03aaf9b260b7" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">CategoryRepository repository = new CategoryRepository();
Category category = repository.Simple(x=>x.CategoryID == categoryId);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Ese método <em>GetById()</em> es solo un ejemplo que pretende demostrar como se puede extender la funcionalidad definida en el repositorio base, en este no se definieron los métodos como virtual para poder sobrescribirlos, pero podría hacerse sin inconveniente.</p>
<p> </p>
<p><strong>Test de categorías </strong>
<hr /></p>
<p>Para validar el funcionamiento de lo codificado hasta el momento crearemos un test que nos ayude.</p>
<p>En el proyecto <em>EF.Test</em> definimos la clase <em>CategoryTest</em></p>
<p><em></em></p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:bee7f518-b259-477a-bb17-48519c7f96a3" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Get_All_Category()
{
CategoryRepository repoCategory = new CategoryRepository();
Category categoryNew = new Category()
{
CategoryName = "CatName1",
Description = "Desc1"
};
repoCategory.Create(categoryNew);
var categoryList = repoCategory.GetAll();
Assert.AreEqual(categoryList.Count, 1);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p><em></em></p>
<p>Al ejecutar el Test si la base de dato no existe EF la creara por nosotros, en este caso la tabla de categorías define los campos:</p>
<p><a href="http://lh5.ggpht.com/-9r8IviEZvow/UdHLlJ5zZFI/AAAAAAAABAQ/TH_kAIZ1euU/s1600-h/image15.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh6.ggpht.com/-BYA7O6i93Es/UdHLmkvOFCI/AAAAAAAABAY/6TxBL7GINcw/image_thumb9.png?imgmax=800" width="413" height="203" /></a></p>
<p>EF define convenciones que pueden utilizarse para no tener que configurar nada de la entidad, por ejemplo si definimos una propiedad que lleve el nombre de la entidad mas ID, esta automáticamente será tomada como clave.</p>
<p> </p>
<p><strong>Cambiar estructura tabla </strong>
<hr /></p>
<p>Cualquier diferencia con lo especificado en la conversión requiere definición, es aquí donde entra en juego el <em>OnModelCreating.</em> </p>
<p>En la siguiente código se agrega al contexto la especificación a las propiedades de la entidad</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:80960520-00cc-497c-88eb-56413a4fa919" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public class NorthWindContext : DbContext
{
public NorthWindContext()
: base("NorthwindDb")
{
}
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CategoryMap());
base.OnModelCreating(modelBuilder);
}
}
public class CategoryMap : EntityTypeConfiguration<Category>
{
public CategoryMap()
{
ToTable("Categories");
HasKey(c => c.CategoryID);
Property(c => c.CategoryID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(c => c.CategoryName).IsRequired().HasMaxLength(15);
Property(c => c.Description).HasColumnType("ntext");
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Al ejecutar nuevamente el Test la definición de la tabla habrá cambiado:</p>
<p><a href="http://lh4.ggpht.com/-zHnro_-vPHw/UdHLnQwNvhI/AAAAAAAABAg/u1FWxJ2LYZ4/s1600-h/image21.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-1ga8nSZYtdI/UdHLosIcm8I/AAAAAAAABAo/Omtb3yIuybc/image_thumb13.png?imgmax=800" width="422" height="201" /></a></p>
<p>En este caso especificar la clave como identity es redundante, ya que por convención EF puede deducirlo, aunque es bueno conocer las opciones de mapping para los casos en donde no se ajuste a la convención.</p>
<p> </p>
<p><strong>Análisis de las consulta (Sql Profiler) </strong>
<hr /></p>
<p>Para poder validar las consultas que Entity Framework genera contra la base de datos será necesario utilizar el Sql Profiler</p>
<p>Una vez ejecutada la aplicación el primer paso crea un nuevo Trace</p>
<p><a href="http://lh5.ggpht.com/-PD-Wl3lQkrg/UdHLps_dsvI/AAAAAAAABAw/QUyjHmVUY4c/s1600-h/image11.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-HrvceQlRKik/UdHLq-of23I/AAAAAAAABA4/lPCjDe9HczI/image_thumb6.png?imgmax=800" width="187" height="141" /></a></p>
<p>En el cuadro de configuración podremos definir un nombre del trace, además de los filtros y columnas de información que necesitemos para realizar el seguimiento, en este el combo “Use the template” se selecciono un témplate que solo nos muestre los datos relativos a las consultas sql</p>
<p><a href="http://lh6.ggpht.com/-DlOSReZjmTI/UdHLsHP8X0I/AAAAAAAABBA/e5zxDrf1j9A/s1600-h/SNAGHTML70825f7c6.png" target="_blank"><img title="SNAGHTML70825f7c" style="display: inline" alt="SNAGHTML70825f7c" src="http://lh4.ggpht.com/-u-uYeoctS-4/UdHLtn9oEYI/AAAAAAAABBI/89x34WlX3AU/SNAGHTML70825f7c_thumb3.png?imgmax=800" width="522" height="339" /></a></p>
<p>También se define el nombre de la base de datos que necesitamos analizar, esto lo conseguimos mediante la definición de un filtro.</p>
<p><a href="http://lh4.ggpht.com/-M2x3yWVo4Tw/UdHLvPb6EqI/AAAAAAAABBQ/-vM0o3g2VJs/s1600-h/SNAGHTML7074e5f97.png" target="_blank"><img title="SNAGHTML7074e5f9" style="display: inline" alt="SNAGHTML7074e5f9" src="http://lh4.ggpht.com/-mXf8EjzRNdE/UdHLw3LfSzI/AAAAAAAABBY/2nQfuewUqJc/SNAGHTML7074e5f9_thumb4.png?imgmax=800" width="512" height="336" /></a></p>
<p>El ultimo paso solo será inicia el trace con el botón “Run”</p>
<p> </p>
<p><strong>Obtener todas las entidades </strong>
<hr /></p>
<p>Empezaremos definiendo un test que no permita validar el recuperar todas las entidades registradas.</p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a88f5d35-06fb-40d9-945d-0b23023ed22e" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Get_All_Category()
{
CategoryRepository repoCategory = new CategoryRepository();
Category categoryNew = new Category()
{
CategoryName = "CatName1",
Description = "Desc1"
};
repoCategory.Create(categoryNew);
var categoryList = repoCategory.GetAll();
Assert.AreEqual(categoryList.Count, 1);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>si analizamos en el Sql Profiler veremos que EF genera la query</p>
<p><a href="http://lh3.ggpht.com/-40G5EyFM-us/UdHLyPOVKDI/AAAAAAAABBg/3h-TDoMhvlA/s1600-h/image6.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh3.ggpht.com/-f9EzROQI8Zo/UdHLzUuE_WI/AAAAAAAABBo/kZhj8jCNTnQ/image_thumb3.png?imgmax=800" width="428" height="157" /></a></p>
<p>En el ejemplo se instancia al repositorio directamente y se utilizan los métodos para crear o recuperar la entidad.</p>
<p> </p>
<p><strong>Obtener una entidad por el ID </strong>
<hr /></p>
<p>Se hará uso del método Single() definido en el repository indicando el lambda que filtrara la entidad por el ID</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d97215d5-dde6-4b12-ae53-3be42dfa5545" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Get_SingleById_Category()
{
CategoryRepository repoCategory = new CategoryRepository();
Category categoryNew = new Category()
{
CategoryName = "CatName1",
Description = "Desc1"
};
repoCategory.Create(categoryNew);
var categorySel = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID);
Assert.IsNotNull(categorySel);
Assert.AreEqual(categorySel.CategoryID, categoryNew.CategoryID);
Assert.AreEqual(categorySel.CategoryName, categoryNew.CategoryName);
Assert.AreEqual(categorySel.Description, categoryNew.Description);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La query que genera EF en el profiler será</p>
<p><a href="http://lh4.ggpht.com/-OtPSgWBGzUg/UdHLz4BT1cI/AAAAAAAABBw/Yuu_dtyccrI/s1600-h/image23.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-UltHT88-UX8/UdHL1W0BuzI/AAAAAAAABB4/cmh-lPdBh7c/image_thumb14.png?imgmax=800" width="496" height="140" /></a></p>
<p>Se puede apreciar claramente la definición del filtro en el WHERE al cual se le asigna el parámetro del Id</p>
<p> </p>
<p><strong>Eliminar una entidad </strong>
<hr /></p>
<p>Para eliminare la entidad simplemente hace falta definir el id de la misma, no es obligatorio recuperar la entidad para poder eliminarla</p>
<p>En este caso como estamos en un mismo test method donde se crea y elimina en una misma secuencial para poder validar el código no tenga tanto sentido, pero es necesario entender que con solo crear una nueva entidad en donde solo se asigne el id alcanza para poder llevar a cabo la finalidad.</p>
<p> </p>
<div id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:0eb02974-f069-4dcc-9b12-fea97e1f4ae8" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">[TestMethod]
public void Delete_ById_Category()
{
CategoryRepository repoCategory = new CategoryRepository();
//
//creamos una nueva categoria
//
Category categoryNew = new Category()
{
CategoryName = "CatName2",
Description = "Desc2"
};
repoCategory.Create(categoryNew);
//
//la eliminamos
//
repoCategory.Delete(new Category() { CategoryID = categoryNew.CategoryID });
//
// se recupera para validar que no exista
//
var categorySel = repoCategory.GetById(categoryNew.CategoryID);
Assert.IsNull(categorySel);
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>En la definición del test se crea una entidad nueva, pero solo se utiliza el id para crear una entidad nueva que se adjunta al contexto para poder asignar el estado</p>
<p><em>context.Entry(entity).State = EntityState.Deleted;</em></p>
<p> </p>
<p>analizando con el profiler podremos ver como EF genera la instrucción sql para eliminar el registro</p>
<p><a href="http://lh5.ggpht.com/-keS0iOnrE9U/UdHL3Z_aNZI/AAAAAAAABCA/Ki1MXfwZboU/s1600-h/image29.png" target="_blank"><img title="image" style="display: inline" alt="image" src="http://lh4.ggpht.com/-vXzzjm-hjN8/UdHL48b7-lI/AAAAAAAABCI/RgfaX-sbOQU/image_thumb18.png?imgmax=800" width="512" height="116" /></a></p>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p>El código fue confeccionado con Visual Studio 2012, utilizando Entity Framework 5</p>
<p>La base de datos es creada de forma automática por el mismo Entity Framework, solo es necesario definir el connectionstring al servicio de sql server</p>
<table cellspacing="0" cellpadding="2" width="400" border="0"><tbody>
<tr>
<td valign="top" width="200">[C#]
<br /><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21377&authkey=AOTib4mf7VV4SUQ" frameborder="0" width="98" scrolling="no"></iframe></td>
<td valign="top" width="200"> </td>
</tr>
</tbody></table> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com80tag:blogger.com,1999:blog-7361892840793499128.post-53174515605040668722013-05-31T11:15:00.001-07:002013-05-31T11:15:50.327-07:00[Dynamic CRM] Integrar Google Maps – PopUp Mapa Web (2/2)<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Se continua el artículo</p> <h5><a href="http://ltuttini.blogspot.com.ar/2013/05/dynamic-crm-integrar-google-maps-popup.html"><font style="font-weight: normal">[Dynamic CRM] Integrar Google Maps – PopUp Mapa Web (1/2)</font></a></h5> <p> </p> <p><strong>Publicar Web Resources</strong> <hr /></p> <p>El primer paso ser crear una nueva solución.</p> <p><a href="http://lh6.ggpht.com/-cgKnA4GTe0k/Uajocvw_iiI/AAAAAAAAA7M/g3xBMzx4mRU/s1600-h/SNAGHTML396706156.png" target="_blank"><img style="display: inline" title="SNAGHTML39670615" alt="SNAGHTML39670615" src="http://lh4.ggpht.com/-Lqvrrr__V9E/UajodlphQrI/AAAAAAAAA7U/Yzq05aAj3Pk/SNAGHTML39670615_thumb3.png?imgmax=800" width="387" height="328" /></a></p> <p>En el cuadro de creación de la solución se define solo algunos campos </p> <p><a href="http://lh3.ggpht.com/-xw4coYkB80s/Uajoe_Xf34I/AAAAAAAAA7c/hB49uIfnqS4/s1600-h/SNAGHTML3968906a6.png" target="_blank"><img style="display: inline" title="SNAGHTML3968906a" alt="SNAGHTML3968906a" src="http://lh4.ggpht.com/-7YmDaiEGyTs/UajoiGFHJtI/AAAAAAAAA7k/udDJfGhA8OQ/SNAGHTML3968906a_thumb3.png?imgmax=800" width="525" height="229" /></a></p> <p>Editando la solución se irán agregando los js y html que requiere el mapa</p> <p><a href="http://lh4.ggpht.com/-RajC0Ej_dB4/UajojadGhnI/AAAAAAAAA7s/1OGGoy1gehw/s1600-h/SNAGHTML396a861d6.png" target="_blank"><img style="display: inline" title="SNAGHTML396a861d" alt="SNAGHTML396a861d" src="http://lh6.ggpht.com/-aajMmVDTxto/UajokdvASZI/AAAAAAAAA70/Y-gNQ6RP34Q/SNAGHTML396a861d_thumb3.png?imgmax=800" width="487" height="330" /></a></p> <p>Creamos un ítem por cada archivo </p> <p><a href="http://lh5.ggpht.com/--YCt7lht6b0/Uajolc5Hd4I/AAAAAAAAA78/z44xgIxO7og/s1600-h/SNAGHTML397059c07.png" target="_blank"><img style="display: inline" title="SNAGHTML397059c0" alt="SNAGHTML397059c0" src="http://lh6.ggpht.com/-Jfbvz0uPrO0/UajomLGdNtI/AAAAAAAAA8E/_cvYLA1pyEs/SNAGHTML397059c0_thumb4.png?imgmax=800" width="653" height="301" /></a></p> <p>El listado final de archivos que conforman la solución seria:</p> <p><a href="http://lh5.ggpht.com/-ec-I1g5EsAc/Uajom9XhdLI/AAAAAAAAA8M/lwtPMjS_pkM/s1600-h/SNAGHTML397ba5326.png" target="_blank"><img style="display: inline" title="SNAGHTML397ba532" alt="SNAGHTML397ba532" src="http://lh5.ggpht.com/-soQadlxt5L8/UajoovD6wDI/AAAAAAAAA8U/ehg9mRObWU4/SNAGHTML397ba532_thumb3.png?imgmax=800" width="541" height="288" /></a></p> <p>Como paso final se publica</p> <p> </p> <p><strong>Edición de la Ribbon toolbar </strong> <hr /></p> <p>Para agregar un nuevo botón en la toolbar del menú de CRM se hará uso de una utilidad de Codeplex</p> <p><a href="http://crmvisualribbonedit.codeplex.com/">CRM 2011 Visual Ribbon Editor</a></p> <p>El primer paso será configurar la conexión al CRM desde la herramienta</p> <p><a href="http://lh5.ggpht.com/-h-G7fYSGhMQ/UajoqtZGH2I/AAAAAAAAA8c/1AAcB2hUPtg/s1600-h/SNAGHTML398171b46.png" target="_blank"><img style="display: inline" title="SNAGHTML398171b4" alt="SNAGHTML398171b4" src="http://lh5.ggpht.com/-FSaETJuL_24/UajosPyQA6I/AAAAAAAAA8k/MC1m0PmVxWE/SNAGHTML398171b4_thumb3.png?imgmax=800" width="404" height="321" /></a></p> <p>Una vez conectado usamos el botón “Open” para que se desplegué la selección de la entidad a la cual se quiere editar el ribbon</p> <p><a href="http://lh5.ggpht.com/-VEnzVDJFA34/Uajos7LJW2I/AAAAAAAAA8s/8tXQ-et6DxQ/s1600-h/SNAGHTML3983c2df6.png" target="_blank"><img style="display: inline" title="SNAGHTML3983c2df" alt="SNAGHTML3983c2df" src="http://lh6.ggpht.com/-nvhlN4RhcrI/Uajot9kuuRI/AAAAAAAAA80/VUq3KQ2Y7os/SNAGHTML3983c2df_thumb3.png?imgmax=800" width="407" height="333" /></a></p> <p>Seleccionamos “account” y aceptamos. En el combo del tipo de ribbon disponibles para la entidad seleccionamos Homepage, la herramienta nos mostrara la representación visual de la toobar actual</p> <p><a href="http://lh4.ggpht.com/-zhlF1wS_5w8/UajouloBBZI/AAAAAAAAA88/rOkHQQFb2Fs/s1600-h/SNAGHTML398551a66.png" target="_blank"><img style="display: inline" title="SNAGHTML398551a6" alt="SNAGHTML398551a6" src="http://lh6.ggpht.com/-9cwRCwzEwGM/UajovucvCyI/AAAAAAAAA9E/AxDLw-luHkY/SNAGHTML398551a6_thumb3.png?imgmax=800" width="429" height="201" /></a></p> <p>Usaremos el botón “New Button” para crear nuestro botón que lanzara el mapa.</p> <p>La definición del botón tiene asociada una imagen que previamente publicamos como recurso</p> <p><a href="http://lh4.ggpht.com/-Xazr2HDuH5w/Uajowxaof5I/AAAAAAAAA9M/MCsgIXaKswY/s1600-h/SNAGHTML398a0e128.png" target="_blank"><img style="display: inline" title="SNAGHTML398a0e12" alt="SNAGHTML398a0e12" src="http://lh4.ggpht.com/-cx8XgbA8qfk/UajoyN0DyrI/AAAAAAAAA9U/E9y0WCtfvac/SNAGHTML398a0e12_thumb5.png?imgmax=800" width="664" height="385" /></a></p> <p>La solapa “Action” nos permitirá definir que función javascript será invocada al presionar el boton</p> <p>Aquí se hará uso un archivo <em>Mapa.js</em> el cual define las funciones responsables de lanzar el popup de la ventana que contiene el mapa</p> <p> </p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:0f6f1f1d-4355-44ac-8ca4-a341ff69f884" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">function openMapStateProvince()
{
window.open('WebResources/new_Mapahtml?Data=grupo%3Dstateorprovince');
}
function openMapTerritory()
{
window.open('WebResources/new_Mapahtml?Data=grupo%3Dterritory');
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Estas funciones javascript definen la propiedad por la cual se agruparan las cuentas y definen los colores de las marcas</p>
<p><a href="http://lh6.ggpht.com/-M2TPOyMPuW8/UajozcdUqAI/AAAAAAAAA9c/8A-YmzuxuJg/s1600-h/SNAGHTML399220b77.png" target="_blank"><img style="display: inline" title="SNAGHTML399220b7" alt="SNAGHTML399220b7" src="http://lh3.ggpht.com/-1kuV6oFcVWA/Uajo01EXAxI/AAAAAAAAA9k/bgTnBoPc238/SNAGHTML399220b7_thumb4.png?imgmax=800" width="676" height="440" /></a></p>
<p> </p>
<p>El ultimo paso será grabar las modificaciones usando el botón “Save”, esto demorara un rato en aplicar los cambio.</p>
<p> </p>
<p><strong>Conclusión </strong>
<hr /></p>
<p>Una vez concluida la publicación de los recursos y agregado el botón en la ribbon toolbar solo queda lanzar el mapa y disfrutar del resultado.</p>
<p>Recursos utilizados: </p>
<p><a href="https://developers.google.com/maps/documentation/javascript?hl=es">Versión 3 del API de JavaScript de Google Maps</a></p>
<p><a href="http://www.maestrosdelweb.com/editorial/consejos-practicos-infowindow-google-maps/">Consejos prácticos para el InfoWindow en Google Maps</a></p>
<p><a href="https://developers.google.com/maps/documentation/javascript/overlays?hl=es#drawing_events">Superposiciones</a></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com2tag:blogger.com,1999:blog-7361892840793499128.post-59965801978870528732013-05-31T08:37:00.001-07:002013-05-31T08:37:55.415-07:00[Dynamic CRM] Integrar Google Maps – PopUp Mapa Web (1/2)<p> </p> <p><strong>Introducción </strong> <hr /></p> <p>Al trabajar con contactos un requerimiento bastante buscado se relaciona con la localización o distribución de estos en un mapa, poder desplegar los cliente en una distribución geográfica da una idea de área de cobertura de ventas.</p> <p>El objetico del articulo será justamente poder despegar en un grafico dinámico los cliente listados en Dynamic CRM, se contara con un botón en la toobox, el cual abrirá una ventana popup del browser con el mapa y los puntos representando cada cliente.</p> <p><a href="http://lh3.ggpht.com/-fcT3K62QClk/UajDdx-_UQI/AAAAAAAAA5E/W0yRLEjpYIw/s1600-h/SNAGHTML333919766.png" target="_blank"><img style="display: inline" title="SNAGHTML33391976" alt="SNAGHTML33391976" src="http://lh3.ggpht.com/-Ak2xlM0ZNBA/UajDfHSOTAI/AAAAAAAAA5M/JZ3Xb8LfcHc/SNAGHTML33391976_thumb3.png?imgmax=800" width="607" height="233" /></a></p> <p> </p> <p><a href="http://lh4.ggpht.com/-jAYKfNBZjgQ/UajDjb2u9RI/AAAAAAAAA5U/8d-q9Dbv9bo/s1600-h/image10.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-9yPKMr8VVes/UajDluwz-hI/AAAAAAAAA5c/Sjujhbda_K8/image_thumb6.png?imgmax=800" width="623" height="324" /></a></p> <p>Durante la confección del articulo no solo se explicara como generar el código que genere el grafico, sino que también incluirá su publicación e integración dentro de Dynamic CRM.</p> <p>Comenzaremos analizando el código que permite hace ruso de las API de Google Maps, para continuar luego con la integración de este a CRM.</p> <p> </p> <p><strong>Estructura del Código </strong> <hr /></p> <p>Lo primero que haremos será integrar código de la API de Google Maps con javascript, pero será necesario consultar los servicio de Dynamic CRM para recuperar los datos del fetchxml que se define en la lista de clientes.</p> <p>En un proyecto web crearemos varios js y un html que contendrá el mapa</p> <p><a href="http://lh6.ggpht.com/-NwY17gtSaSw/UajDmYdEzjI/AAAAAAAAA5k/rrzA8LY7Cnw/s1600-h/SNAGHTML333bd3525.png" target="_blank"><img style="display: inline" title="SNAGHTML333bd352" alt="SNAGHTML333bd352" src="http://lh6.ggpht.com/-X7SEUAWGy2M/UajDno4nNfI/AAAAAAAAA5s/qo2c3ZJbppQ/SNAGHTML333bd352_thumb2.png?imgmax=800" width="214" height="240" /></a></p> <p> </p> <p><strong></strong></p> <strong>Invocar Servicios CRM desde javascript</strong> <hr /> <p>En el archivo <em>CRMHelper.js</em> se encontrara toda al funcionalidad requerida para invocar el servicio de CRM.</p> <p>Se necesitara de una query xml conocida en CRM como fetchxml para conocer que cuentas estaban listadas cuando el usuario presiono en el icono del mapa, estas cuentas serán posicionadas como puntos.</p> <p>El primer paso es conocer como consultar un servicio CRM mediante javascript.</p> <p> </p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fea01142-7748-44fe-9a2e-e6bf41e1c942" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">//toma el root de la url del sitio CRM
var context = window.parent.opener.Xrm.Page.context;
var serverUrl = context.getServerUrl();
if (serverUrl.match(/\/$/)) {
serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}
//ejecuta la consulta fetchxml al servicio de CRM
function FetchResultsXml(fetchXml) {
var request = '<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">' +
'<s:Body>'+
'<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' +
'<request i:type="b:RetrieveMultipleRequest" ' +
' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' +
' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' +
'<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' +
'<b:KeyValuePairOfstringanyType>' +
'<c:key>Query</c:key>' +
'<c:value i:type="b:FetchExpression">' +
'<b:Query>'+ CrmEncodeDecode.CrmXmlEncode(fetchXml) +
'</b:Query>' +
'</c:value>' +
'</b:KeyValuePairOfstringanyType>' +
'</b:Parameters>' +
'<b:RequestId i:nil="true"/>' +
'<b:RequestName>RetrieveMultiple</b:RequestName>' +
'</request>' +
'</Execute>' +
'</s:Body></s:Envelope>';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serverUrl + "/XRMServices/2011/Organization.svc/web", false);
xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
xmlhttp.send(request);
return xmlhttp.responseXML;
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Aquí se define el xml que define el mensaje SOAP para invocación al servicio de CRM, dentro se ubica el fetchxml.</p>
<p>La url que utilizada para invocar el servicio se descubre por medio de <em>window.parent.opener.Xrm.Page.context,</em> usando la función <em>getServerUrl()</em></p>
<p>El resultado que obtendremos es un xml con los datos de las cuentas, los cuales serán parseados para formar objetos javascript que represente los datos de cada cuenta.  </p>
<p>El las siguientes funciones se parsean el resultado y vuelcan la info. a un array definido en javascript. Resultara mas  simple si se tiene a mano el xml ha procesar, para esto lo que hice fue poner el resultad en un div usando la líneas:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:7675042c-0301-4139-baf9-3e8867620e3e" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var resultCRM = FetchResultsXml(fetchxml);
$('#xmlresult').html(resultCRM.xml);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Se invoca a la función que ejecuta la invocación al servicio, mostrando el resultado en el div, con el Developer Tools del IE (accedemos mediante F12) analizaremos la estructura de tag que deberemos parsear.</p>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:229d7369-1f8f-4902-a84d-d14556a5d276" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">function GetFieldValue(entity, fieldName) {
var vals = entity.getElementsByTagName("a:KeyValuePairOfstringanyType");
for (var j = 0; j < vals.length; j++) {
if (vals[j].getElementsByTagName("b:key")[0].firstChild.nodeValue == fieldName) {
var valueNode = vals[j].getElementsByTagName("b:value");
if (valueNode.length > 0)
if (valueNode[0].childNodes.length == 1)
return valueNode[0].firstChild.nodeValue;
else
return valueNode[0].childNodes[2].firstChild.nodeValue; //se toma el valor del nodo "name"
else
return "";
}
}
}
function GetEntityName(fetchResults) {
var entityElems = fetchResults.getElementsByTagName("a:EntityName");
if (entityElems.length > 0) {
return entityElems[0].firstChild.nodeValue;
}
}
function GetArrayFromFetchResults(fetchResults) {
var entityResults = new Array();
var entityname = GetEntityName(fetchResults);
var entityElems = fetchResults.getElementsByTagName("a:Entity");
for (var i = 0; i < entityElems.length; i++) {
entityResults[i] = new Object();
if (entityname == 'account') {
entityResults[i].id = GetFieldValue(entityElems[i], "accountid");
entityResults[i].name = GetFieldValue(entityElems[i], "name");
entityResults[i].addressname = GetFieldValue(entityElems[i], "address1_line1") == null ? "" : GetFieldValue(entityElems[i], "address1_line1");
entityResults[i].latitude = GetFieldValue(entityElems[i], "address1_latitude");
entityResults[i].longitude = GetFieldValue(entityElems[i], "address1_longitude");
entityResults[i].stateorprovince = GetFieldValue(entityElems[i], "address1_stateorprovince") == null ? "" : GetFieldValue(entityElems[i], "address1_stateorprovince");
entityResults[i].territory = GetFieldValue(entityElems[i], "territoryid") == null ? "" : GetFieldValue(entityElems[i], "territoryid");
}
}
return entityResults;
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La funcionalidad inicia en el método <em>GetArrayFromFetchResults()</em> el cual invoca a <em>GetEntityName()</em> para conocer que entidad se esta trabajando, en al imagen se puede observar el tag que se recupera:</p>
<p><a href="http://lh6.ggpht.com/-8sDsoLltDco/UajDoro7E9I/AAAAAAAAA50/E5jhHK0_-d8/s1600-h/SNAGHTML343cfd926.png" target="_blank"><img style="display: inline" title="SNAGHTML343cfd92" alt="SNAGHTML343cfd92" src="http://lh6.ggpht.com/-LfeISKPrBVE/UajDpqiXQ-I/AAAAAAAAA58/BLp2aKsDOo0/SNAGHTML343cfd92_thumb3.png?imgmax=800" width="522" height="208" /></a></p>
<p>si bien en este ejemplo solo se usa la entidad account podría adaptarse el código para visualidad otro tipo de entidades por eso es bueno conocer de cual se trata.</p>
<p>El siguiente paso será recuperar cada uno de los atributos por cada account. Tomamos el tag “entity” usando la línea:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fa6769e5-27e1-4870-9a46-fc34d7ea32fa" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var entityElems = fetchResults.getElementsByTagName("a:Entity");</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>como se observan son 4, una por cada account de la lista que muestra CRM.</p>
<p><a href="http://lh4.ggpht.com/-ZRWmu3FOvqo/UajDtHk_wZI/AAAAAAAAA6E/Fnk_ExM96mQ/s1600-h/SNAGHTML3440082c6.png" target="_blank"><img style="display: inline" title="SNAGHTML3440082c" alt="SNAGHTML3440082c" src="http://lh3.ggpht.com/-GCrR1SUP8c4/UajDudvhXMI/AAAAAAAAA6M/rZk-yks_GFE/SNAGHTML3440082c_thumb3.png?imgmax=800" width="536" height="218" /></a></p>
<p>Dentro de la función <em>GetFieldValue()</em> accedemos directo al tag <em>KeyValuePairOfstringanyType, </em>la colección de estos tag contiene el valor de cada atributo, en este punto hay que diferenciar entre datos simple del CRM como ser, por ejemplo, el name del account.</p>
<p><a href="http://lh6.ggpht.com/-zibYQx92vkI/UajDvUOUyxI/AAAAAAAAA6U/hKVy3EtFJQk/s1600-h/SNAGHTML34459eb16.png" target="_blank"><img style="display: inline" title="SNAGHTML34459eb1" alt="SNAGHTML34459eb1" src="http://lh5.ggpht.com/-Ycc8txl670k/UajDwjjSVOI/AAAAAAAAA6c/mkDgPoAGSNM/SNAGHTML34459eb1_thumb3.png?imgmax=800" width="548" height="217" /></a></p>
<p>en donde el value es un valor simple, esto se recupera mediante la líneas:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d6f281fc-3bdf-4b21-99d8-2d6e87e70a85" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">if (valueNode[0].childNodes.length == 1)
return valueNode[0].firstChild.nodeValue;</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>ya que el tag “value” no tendrá ningún nodo hijo.Pero hay otros atributos complejos como ser el territorio, al ser una entidad relacionada el valor a recuperar tiene otra estructura de nodos.</p>
<p><a href="http://lh5.ggpht.com/-Qfwt_qT_HcA/UajDxlImPuI/AAAAAAAAA6k/F3oe0Uo4Avs/s1600-h/SNAGHTML3449d3386.png" target="_blank"><img style="display: inline" title="SNAGHTML3449d338" alt="SNAGHTML3449d338" src="http://lh6.ggpht.com/--4-EpmRLUXQ/UajDyU2g3kI/AAAAAAAAA6s/wQChWOEP_TM/SNAGHTML3449d338_thumb3.png?imgmax=800" width="377" height="265" /></a></p>
<p>es por eso que pasara por al línea:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fcb0f9b3-df8e-452a-a01b-9fae0bed8fc6" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">return valueNode[0].childNodes[2].firstChild.nodeValue; //se toma el valor del nodo "name"</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>una vez entendido como parsear el xml solo queda armar el array con las propiedades de la entidad, esto es justamente lo que retorna <em>GetArrayFromFetchResults()</em></p>
<p> </p>
<p><strong>Uso de las Google Map API </strong>
<hr /></p>
<p>Luego de obtener los datos se procede a configurar el mapa usando las API de Google MAP, esto código se define en el archivo Mapa.html</p>
<p>Lo primero será declarar las librerías necesarias</p>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:305f5b38-79ba-4234-9e11-47f801a658b2" class="wlWriterEditableSmartContent"><pre class="brush: xml; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "><script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script src="ClientGlobalContext.js.aspx" type="text/javascript"></script>
<script src="new_jquery1.9" type="text/javascript"></script>
<script src="new_CRMHelper" type="text/javascript"></script>
<script src="new_linqjs" type="text/javascript"></script>
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Jquery es una librería necesaria. También vemos el link a las API de Google Map.</p>
<p>Pero seguro notaran una de nombre linqjs, esta será usada para poder agrupar los datos desde javascript, se trata de:</p>
<p><a href="http://linqjs.codeplex.com/">linq.js - LINQ for JavaScript</a></p>
<p>Nota: los script que llevan el prefijo <em>new_</em> es porque fueron subidos como resource al propio CRM, este paso lo veremos mas adelante al publicar los js y html</p>
<p>El código que define el mapa es el siguiente:</p>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:cf6a525f-6a56-45b4-9f2f-0328347e8e5f" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "> <script type="text/javascript">
$(function() {
try {
var data = decodeURIComponent($.getUrlVar('Data'));
var grupo = data.split('=')[1];
var fetchxml = window.opener.parent.document.getElementById('contentIFrame').contentDocument.getElementById('effectiveFetchXml').attributes['value'].nodeValue;
var resultCRM = FetchResultsXml(fetchxml);
$('#xmlresult').html(resultCRM.xml);
var resultArray = GetArrayFromFetchResults(resultCRM);
//se define la lista de colores que puede tomar los marker
var listcolor = new Array('FE7569', '0404B4', 'FFFF00', '088A08', '01DFD7', '8A0886', '1C1C1C');
var mapOptions = {
zoom: 4,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
var bounds = new google.maps.LatLngBounds();
var infowindow = new google.maps.InfoWindow();
var index = 0;
Enumerable.From(resultArray)
.GroupBy('$.' + grupo, '', function(key, group) { return { sucu: key, group: group} })
.ForEach(function(x) {
var pinImage = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + listcolor[index],
new google.maps.Size(21, 34),
new google.maps.Point(0, 0),
new google.maps.Point(10, 34));
x.group.ForEach(function(y) {
var marker = new google.maps.Marker({
position: new google.maps.LatLng(y.latitude, y.longitude),
icon: pinImage,
map: map,
html: "<b>" + y.name + "</b>
Direccion: " + y.addressname + "
Estado: " + y.stateorprovince + "
Territorio: " + y.territory
});
google.maps.event.addListener(marker, "click", function() {
infowindow.setContent(this.html);
infowindow.open(map, this);
});
bounds.extend(marker.position);
});
index++;
if (index == listcolor.length)
index = 0;
});
map.fitBounds(bounds);
}
catch (err) {
var txt = "There was an error on this page.\n\n";
txt += "Error description: " + err.message + "\n\n";
txt += "Click OK to continue.\n\n";
alert(txt);
}
});
</script>
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Ahora vamos a ir analizando las diferentes línea que permite crear el mapa. Empezamos tomando de la url el valor de la querystring, el cual define como agruparemos para generar en el mapa los puntos de diferentes colores</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:3a2e3580-2ebd-463d-9a90-90e65247e880" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var data = decodeURIComponent($.getUrlVar('Data'));
var grupo = data.split('=')[1];
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Luego tomaremos el fetchxml </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:1f1d9032-eaed-4b70-a188-0d69c95f4a48" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var fetchxml = window.opener.parent.document.getElementById('contentIFrame').contentDocument.getElementById('effectiveFetchXml').attributes['value'].nodeValue;
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>la línea es bastante larga pues el dato se encuentra bastante escondido, si analizamos el html del CRM con el Developer Tools y buscamos “effectiveFetchXml” veremos que el tag se encuentra dentro de un iframe.</p>
<p>De esta forma obtenemos valor que conforma el xml que utiliza CRM para definir las cuentas que lista en pantalla.</p>
<p><a href="http://lh4.ggpht.com/-sMtwm2VQv1U/UajDzFUjcFI/AAAAAAAAA60/O9liIP2Bmm0/s1600-h/SNAGHTML3473800b6.png" target="_blank"><img style="display: inline" title="SNAGHTML3473800b" alt="SNAGHTML3473800b" src="http://lh4.ggpht.com/-F-Xq9S5T9ys/UajD0BMTmmI/AAAAAAAAA68/bVd9WSfyQ4Q/SNAGHTML3473800b_thumb3.png?imgmax=800" width="462" height="393" /></a></p>
<p> </p>
<p>A continuación lanzamos la consulta al servicio con el código analizado en la sección previa.</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:c0a3a7b9-cb48-4a3a-98ce-03996bd33f7f" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var resultCRM = FetchResultsXml(fetchxml);
//$('#xmlresult').html(resultCRM.xml);
var resultArray = GetArrayFromFetchResults(resultCRM);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Se declaran las variables que definen el mapa:</p>
<ul>
<li><strong>mapOptions</strong>, define información global del mapa, en este caso zoom y tipo </li>
<li><strong>map</strong>, contiene la instancia del div que contendrá el mapa </li>
<li><strong>bounds</strong>, se utiliza para general la región que permita centrar la vista, de por si el centrado no es automático, es necesario indicar las posiciones que conforman la región en que se quiere centrar el foco visual </li>
<li><strong>infowindow</strong>, define el popup con al información contextual para cada marca </li>
</ul>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:69df18f5-23b3-4f74-a54a-3928c053a7a8" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var mapOptions = {
zoom: 4,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
var bounds = new google.maps.LatLngBounds();
var infowindow = new google.maps.InfoWindow();
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>La siguiente líneas serán las encargadas de agrupar e iterar los datos de las cuentas, es aquí donde se hace uso de la librería linq.js para poder agrupar la lista de cuenta por la propiedad definida en la url.</p>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:38395958-3c9c-4292-9791-3ce2b7f7f663" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">Enumerable.From(resultArray)
.GroupBy('$.' + grupo, '', function(key, group) { return { sucu: key, group: group} })
.ForEach(function(x) {
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Las líneas de código que se encuentran dentro del ForEach crearan las marcas en el mapa.</p>
<p>Se define la forma del icono, en el link <a href="https://developers.google.com/chart/image/docs/gallery/dynamic_icons#pins" target="_blank">Google Maps Pins</a> se podrá encontrar información sobre la generacion de estos iconos.  </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:b8c2f725-49bc-4f68-8bf5-d739f47fb46c" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">var pinImage = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + listcolor[index],
new google.maps.Size(21, 34),
new google.maps.Point(0, 0),
new google.maps.Point(10, 34));
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Se itera por lo ítem que forman cada grupo, recordemos que cada grupo determina el color de sus ítems</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:86c5037e-d10e-419e-9f82-62b514e606e2" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">x.group.ForEach(function(y) { ....</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Se crea el market, en este se define la posición concreta del punto en el mapa, también se define un contenido asociado cuando se pulse sobre la marca asignando el html que representa el contenido</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:f90c121b-f7bc-41f4-859e-bec1a162f17c" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">
var marker = new google.maps.Marker({
position: new google.maps.LatLng(y.latitude, y.longitude),
icon: pinImage,
map: map,
html: "<b>" + y.name + "</b><br/>Direccion: " + y.addressname + "<br/>Estado: " + y.stateorprovince + "<br/>Territorio: " + y.territory
});
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Se asocia el market con un evento, el cual desplegara el popup con información de la localización. Además se agrega la posición a la lista de bounds para definir el área en que se centrara la grafica.</p>
<p> </p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:d265d2a9-29b0-40bf-bf55-0c92022a5090" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">google.maps.event.addListener(marker, "click", function() {
infowindow.setContent(this.html);
infowindow.open(map, this);
});
bounds.extend(marker.position);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>Luego de terminado el ciclo por cada grupo y cuenta, se asigna los bounds al map</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2f57c76f-4804-4121-a66b-fc0a40f8ba8f" class="wlWriterEditableSmartContent"><pre class="brush: jscript; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">map.fitBounds(bounds);
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Hasta aquí seria todo lo necesario con código para generar el mapa.</p>
<p>En la siguiente parte del artículo veremos como publicar en CRM y asociar a un botón que se encuentra en la ribbon</p>
<p> </p>
<p><strong>Código </strong>
<hr /></p>
<p><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21371&authkey=ADpeiCo59ijgq_A" frameborder="0" width="98" scrolling="no"></iframe></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com0tag:blogger.com,1999:blog-7361892840793499128.post-63025830936120549282013-05-19T12:40:00.001-07:002013-05-19T12:40:48.211-07:00[Reporting Service] [Dynamic CRM] - Integrar con google maps (2/2)<p> </p> <p><strong>Introducción </strong></p> <hr /> <p>Continuamos con el articulo anterior</p> <h5><a href="http://ltuttini.blogspot.com.ar/2013/05/reporting-service-dynamic-crm-integrar.html"><font style="font-weight: normal">[Reporting Service] [Dynamic CRM] - Integrar con google maps (1/2)</font></a></h5> <p> </p> <p><strong>Creación del componente</strong> <hr /></p> <p>Antes de arrancar hay resaltar que no podremos usar .net 4 pues el rdl que estamos editando con VS2008 solo soporta hasta .net 3.5</p> <p>Este punto no es menor, ya que esto nos imposibilita hacer uso de las librerías de CRM SDK, por lo tanto deberemos hacer uso del servicio de WCF para poder consultar al CRM y obtener la info de las cuentas</p> <p>Entonces el primer paso será crear un proyecto del tipo Class Library, remarco la definición de .net 3.5</p> <p><a href="http://lh6.ggpht.com/-_nqKgz1Ckb0/UZkhC8a9_1I/AAAAAAAAAzY/zzwQkz41u60/s1600-h/SNAGHTML2eb3ce997.png" target="_blank"><img style="display: inline" title="SNAGHTML2eb3ce99" alt="SNAGHTML2eb3ce99" src="http://lh4.ggpht.com/-m0rAgWSaPUI/UZkhH12bQMI/AAAAAAAAAzg/PlCCdYRLjoM/SNAGHTML2eb3ce99_thumb4.png?imgmax=800" width="568" height="357" /></a></p> <p>En este punto se puede usar tanto VS 2008 o 2010, en este caso decidí usar 2010 pero remarcando la opción del framework utilizado.</p> <p>En el proyecto veremos una clase que representa el proxy del servicio, esta se creo mediante el uso de la utilidad</p> <p><a href="http://msdn.microsoft.com/es-AR/library/7h3ystb6%28v=vs.90%29.aspx" target="_blank">Herramienta Lenguaje de descripción de servicios Web (Wsdl.exe)</a></p> <p>utilizando la url: http://<sitio>:<puerto>/ContosoHQ/XRMServices/2011/Organization.svc</p> <p>en este caso la organización de ejemplo que estamos usando es ContosoHQ, pero esto se debe reemplazar por el que estén utilizando. La clase resultante es la OrganizationService.cs</p> <p><a href="http://lh3.ggpht.com/-BgpFQLrU-K8/UZkhJI6xJWI/AAAAAAAAAzo/hLgWcRY7EHA/s1600-h/SNAGHTML3922419d6.png" target="_blank"><img style="display: inline" title="SNAGHTML3922419d" alt="SNAGHTML3922419d" src="http://lh3.ggpht.com/-vpRU7e-CyWw/UZkhO7iOQ6I/AAAAAAAAAzw/EfV-pFAd3ts/SNAGHTML3922419d_thumb3.png?imgmax=800" width="235" height="326" /></a>  </p> <p>Se creo un helper el cual no brindara información de CRM utilizando el servicio, se trata del CRMHelper.cs</p> <p>Se utiliza el fetchxml proveniente del reporte como filtro para conocer que cuentas se están mostrando en el reporte.</p> <p> </p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:5ebd25d5-a821-497a-ab39-9ab3af3cc086" class="wlWriterEditableSmartContent"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public static List<AccountCRM> GetAccounts(string query)
{
try
{
List<AccountCRM> accounts = new List<AccountCRM>();
using (OrganizationServiceClient crmService = new OrganizationServiceClient())
{
EntityCollection myAccounts = crmService.RetrieveMultiple(new FetchExpression() { Query = query });
foreach (Entity entity in myAccounts.Entities)
{
AccountCRM account = new AccountCRM()
{
id = ((XmlText)((XmlNode[])entity.Attributes.First(x => x.key == "accountid").value)[2]).Value,
razonsocial = entity.Attributes.First(x => x.key == "name").value.ToString(),
address = entity.Attributes.First(x => x.key == "address1_line1").value.ToString(),
stateorprovince = entity.Attributes.First(x => x.key == "address1_stateorprovince").value.ToString(),
country = entity.Attributes.First(x => x.key == "address1_country").value.ToString(),
territory = ((EntityReference)entity.Attributes.First(x => x.key == "territoryid").value).Name,
};
//se valida si la key puede no retornarse
//si la entidad tiene un valor nulo o vacio el servicio no la retorna como respuesta
//a pesar que este incluida en el fetchxml
var latitud = entity.Attributes.FirstOrDefault(x => x.key == "address1_latitude");
var longitude = entity.Attributes.FirstOrDefault(x => x.key == "address1_longitude");
if (!(latitud == null && longitude == null))
{
account.Position = new GeoPosition()
{
Latitude = Convert.ToDouble(latitud.value),
Longitude = Convert.ToDouble(longitude.value),
};
}
accounts.Add(account);
}
}
return accounts;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
throw;
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El código que sigue hará uso de las cuenta para generar la url el cual permitirá a la api de google maps generar la imagen</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:a5f543f1-a253-48ae-bf1a-2d7dfbf02df7" class="wlWriterEditableSmartContent"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">public static byte[] GetMap(string fetchxml)
{
//asignamos la cultura en en-US para que la puntuacion de la localizacion resuelva correctamente
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
try
{
//obtenemos las cuentas
List<AccountCRM> accounts = CRMHelper.GetAccounts(fetchxml);
if (accounts.Count > 0)
return GoogleStaticMap(accounts);
else
return null;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
return null;
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Una vez ejecutado el fetchexml y recuperada las cuenta se procede armar la url que generara la imagen del mapa</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:4cca3e4a-e360-4c0c-b81f-b2e306ed8fea" class="wlWriterEditableSmartContent"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; ">private static byte[] GoogleStaticMap(List<AccountCRM> accounts)
{
List<string> colors = new List<string>() { "red", "blue", "yellow", "green", "orange" };
try
{
//en la url se define el tamaño de la imagen, formato y tipo de mapa
string url = "http://maps.googleapis.com/maps/api/staticmap?size=900x450&maptype=roadmap&format=jpg{0}&sensor=false";
//solo se procesan las cuentas que tengan geo-posicionamiento
accounts = accounts.Where(x => x.Position != null).ToList();
if (accounts.Count() == 0)
return null;
//se agrupa las cuentas por su territorio
//para definir los colores que se aplicara a cada marca en el mapa
var groupAccounts = accounts.GroupBy(x=> x.territory);
//se recorre cada grupo armando la marca
List<string> markersList = new List<string>();
int colorindex = 0;
foreach (var item in groupAccounts)
{
markersList.Add(string.Format("&markers=color:{0}|{1}", colors[colorindex], string.Join("|", item
.Select(x => string.Format("{0},{1}", x.Position.Latitude, x.Position.Longitude))
.ToArray())
));
colorindex++;
if (colorindex == colors.Count)
colorindex = 0;
}
string markers = string.Join("", markersList.ToArray());
url = string.Format(url, markers);
//se invoca a la url para obtener la imagen del mapa
Uri uri = new Uri(url);
WebClient client = new WebClient();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
Stream stream = request.GetResponse().GetResponseStream();
//se convierte el stream en byte[]
return ReadFullStream(stream, request.GetResponse().ContentLength);
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
throw;
}
}
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p> </p>
<p>Como paso final copiaremos la dll generada en la carpeta del proyecto donde estamos editando el rdl</p>
<p> </p>
<p><strong>Vincular RDL con librería .net</strong></p>
<hr />
<p>Estando en al edición del rdl en el Visual Studio 2008, seleccionamos la opción</p>
<p><a href="http://lh5.ggpht.com/-3624l-4w008/UZkhP6mcFGI/AAAAAAAAAz4/ruI3fb9iu3k/s1600-h/image17.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh3.ggpht.com/-4BTrjwPklCw/UZkhR4Xq2AI/AAAAAAAAAz8/IyKq6_xGtbo/image_thumb11.png?imgmax=800" width="336" height="187" /></a></p>
<p>veremos un dialogo del cual no interesa la opción “Referencias”</p>
<p><a href="http://lh6.ggpht.com/-rid1XJ9micw/UZkhS_k9NVI/AAAAAAAAA0I/54YFw9SLmwY/s1600-h/image18.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh4.ggpht.com/-dh6OLW7sFd8/UZkhVZWNFiI/AAAAAAAAA0Q/-lHRx3on3N8/image_thumb12.png?imgmax=800" width="485" height="378" /></a></p>
<p>Usaremos la opción de “Agregar” para buscar la dll que creamos en el paso anterior</p>
<p><a href="http://lh5.ggpht.com/-XkDduGD2oQY/UZkhWHcvEdI/AAAAAAAAA0Y/MaB-a-L5IRg/s1600-h/image19.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-8dSI3yO9rM8/UZkhXqXe_PI/AAAAAAAAA0g/fbbK8HexYio/image_thumb13.png?imgmax=800" width="391" height="293" /></a></p>
<p> </p>
<p>Arrastramos el control imagen al diseñador del reporte</p>
<p><a href="http://lh5.ggpht.com/-nCotLXIyL9A/UZkhZI0fHrI/AAAAAAAAA0o/1lpIUzr0yxo/s1600-h/image25.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-c-iCrlqrLWM/UZkhcmaicjI/AAAAAAAAA0w/SmWknHYtnxo/image_thumb17.png?imgmax=800" width="175" height="285" /></a></p>
<p>el control el diseñador del reporte nos despliega el dialogo donde podremos definir la formula que invocara al método de nuestra libreria</p>
<p><a href="http://lh6.ggpht.com/-sPnCO8tOkfA/UZkifi0eQCI/AAAAAAAAA1A/ADyg0BMtPwI/s1600-h/SNAGHTML2fdec8687.png" target="_blank"><img style="display: inline" title="SNAGHTML2fdec868" alt="SNAGHTML2fdec868" src="http://lh3.ggpht.com/-szTbCOUt88g/UZkimbKs70I/AAAAAAAAA1I/mNeb5WGgGxY/SNAGHTML2fdec868_thumb4.png?imgmax=800" width="751" height="320" /></a></p>
<p>La formula seria la siguiente:</p>
<p><em>=ContosoGoogleMap.ReportHelper.GetMap(Parameters!CRM_FilteredAccount.Value)</em></p>
<p>como se observa define el namespace + clase + método</p>
<p>al hacer uso del parámetro:  <em>Parameters!CRM_FilteredAccount.Value</em> obtendremos el fetchxml que le llega al reporte, por ejemplo, podría ser algo como:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:4a96c702-6dff-44fc-b6f3-541e500967c2" class="wlWriterEditableSmartContent"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "><fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
<entity name="account">
<all-attributes />
<filter type="and">
<condition attribute="address1_city" operator="eq" value="Buenos Aires" />
</filter>
</entity>
</fetch>
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>El propio CRM envía de forma automática el xml del fetchxml al reporte.</p>
<p> </p>
<p><strong>Configuración Reporting Service </strong></p>
<hr />
<p>1- Se copiara la dll a la carpeta</p>
<p>%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer\bin</p>
<p> </p>
<p>2- Modificar el archivo rssrvpolicy.config de la carpeta</p>
<p>%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer</p>
<p>colocando justo debajo del <CodeGroup> que lleva el $CodeGen$ la definición:</p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2537e2c7-32f1-45d4-a6b9-c15e53b08769" class="wlWriterEditableSmartContent"><pre class="brush: csharp; gutter: false; first-line: 1; tab-size: 4; toolbar: true; "><CodeGroup
class="FirstMatchCodeGroup"
version="1"
PermissionSetName="FullTrust"
Name="ContosoGoogleMapGroup"
Description="">
<IMembershipCondition
class="UrlMembershipCondition"
version="1"
Url="C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin\ContosoGoogleMap.dll"/>
</CodeGroup>
</pre><!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com --></div>
<p>3- Modificar en el <em>rssrvpolicy.config</em> la línea que lleva el <em>Report_Expressions_Default_Permissions</em> validando que el <em>PermissionSetName</em> este en “<em>FullTrust</em>”. </p>
<p><a href="http://lh4.ggpht.com/-itGG192cnWs/UZkisQr5yZI/AAAAAAAAA1Q/NiIKvamZzps/s1600-h/SNAGHTML2feed0c17.png" target="_blank"><img style="display: inline" title="SNAGHTML2feed0c1" alt="SNAGHTML2feed0c1" src="http://lh6.ggpht.com/-jmJvY8giyBw/UZkkYedHKSI/AAAAAAAAA1o/ViVXIXv4xvU/SNAGHTML2feed0c1_thumb4.png?imgmax=800" width="661" height="157" /></a></p>
<p>Esto será necesario para poder escribir a disco, además de poder realizar las invocaciones a las url de CRM y Google, sino se define se obtendrán errores como ser:</p>
<p><font color="#ff0000">Error de solicitud de permiso de tipo 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.</font></p>
<p>o</p>
<p><font color="#ff0000">Error de solicitud de permiso de tipo 'System.Web.AspNetHostingPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'</font></p>
<p> </p>
<p>4 – Modificar el web.config de la carpeta </p>
<p>%ProgramFiles%\Microsoft SQL Server\MSRSXX.<Instance Name>\Reporting Services\ReportServer</p>
<p>en este se debe definir la configuración del <system.serviceModel> que se encuentra en el app.config del archivo de test, este debería estar a nivel del <configuration></p>
<p><a href="http://lh4.ggpht.com/-Pd6dL-HGGuA/UZkkhDNaL-I/AAAAAAAAA1w/7f7PKQsl2g4/s1600-h/SNAGHTML2ff3687e6.png" target="_blank"><img style="display: inline" title="SNAGHTML2ff3687e" alt="SNAGHTML2ff3687e" src="http://lh6.ggpht.com/-LjnLBaPFkLI/UZkkkmOK9rI/AAAAAAAAA14/awkHVm2VNXA/SNAGHTML2ff3687e_thumb3.png?imgmax=800" width="474" height="214" /></a></p>
<p>también se puede definir el <system.diagnostics></p>
<p> </p>
<p>5 - Como paso final seria recomendable reiniciar el servicio de reporting para asegurar que todo lo modificado tome efecto</p>
<p><a href="http://lh6.ggpht.com/-ffW6p56uEhQ/UZkknIpMKqI/AAAAAAAAA2A/xrqPFwZRACM/s1600-h/SNAGHTML2fa75ce86.png" target="_blank"><img style="display: inline" title="SNAGHTML2fa75ce8" alt="SNAGHTML2fa75ce8" src="http://lh6.ggpht.com/-6v1PhHZyoFc/UZkksW2DuWI/AAAAAAAAA2I/oKSY0gS5n18/SNAGHTML2fa75ce8_thumb3.png?imgmax=800" width="559" height="234" /></a></p>
<p> </p>
<p><strong>Publicar del reporte en CRM </strong></p>
<hr />
<p>Una vez que se tenga el rdl modificado con la imagen cuya formula invoca a la librería y el servidor de reporte configurado, se procede a publicar el reporte. Para esta opción volvemos a Dynamic CRM donde editaremos el reporte y usaremos la opción desplegable que nos permite seleccionar un archivo para subirlo</p>
<p><a href="http://lh4.ggpht.com/-vdQkHPnKXko/UZknbQn19DI/AAAAAAAAA2k/3Qb_Kgtej2s/s1600-h/SNAGHTML2ffd86d76.png" target="_blank"><img style="display: inline" title="SNAGHTML2ffd86d7" alt="SNAGHTML2ffd86d7" src="http://lh6.ggpht.com/-7g_ZgydJy60/UZkoiYtXEgI/AAAAAAAAA20/5bBmnFm8D0E/SNAGHTML2ffd86d7_thumb3.png?imgmax=800" width="687" height="283" /></a></p>
<p>Se localiza el rdl, guardar y lanzar el reporte</p>
<p><a href="http://lh4.ggpht.com/-sQy2oTNoXJk/UZkpK1w3niI/AAAAAAAAA28/uOoAcLmqeCU/s1600-h/SNAGHTML2ffee2db6.png" target="_blank"><img style="display: inline" title="SNAGHTML2ffee2db" alt="SNAGHTML2ffee2db" src="http://lh5.ggpht.com/-fAbKLmSL0OA/UZkqAczekhI/AAAAAAAAA3E/VNFmNJ82uxs/SNAGHTML2ffee2db_thumb3.png?imgmax=800" width="685" height="307" /></a></p>
<p>El reporte resultante nos mostrara un listado de cliente y debajo el mapa con su localización, la cual pintara un color por el territorio al que pertenecen</p>
<p><a href="http://lh4.ggpht.com/-acrGkkRGGsw/UZkqkLZaq4I/AAAAAAAAA3M/Rizt1dM8WIM/s1600-h/image32.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-BeJmUHPINoo/UZkqvDNoscI/AAAAAAAAA3U/RCVgtuiYhPg/image_thumb22.png?imgmax=800" width="554" height="436" /></a></p>
<p> </p>
<p><strong>Recursos </strong></p>
<hr />
<h3><font style="font-weight: normal" size="2"><a href="https://developers.google.com/maps/documentation/staticmaps/?hl=es" target="_blank">Guía para desarrolladores de la versión 2 del API de Google Static Maps</a></font></h3>
<p>ver la sección de titulo “Marcadores”</p>
<p><a href="http://social.msdn.microsoft.com/Forums/en-US/sqlreportingservices/thread/1ea13e43-0c99-4210-a312-5d8a0c234b37" target="_blank">Reporting Service - .net dll integration - problem security access with web service and file</a></p>
<p> </p>
<p><strong>Código </strong></p>
<hr />
<p><iframe height="120" src="https://skydrive.live.com/embed?cid=5C82AA0C9BBAF5B3&resid=5C82AA0C9BBAF5B3%21365&authkey=AE4jrDLeZZUKj9U" frameborder="0" width="98" scrolling="no"></iframe></p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com0tag:blogger.com,1999:blog-7361892840793499128.post-8965537259222806102013-05-19T08:22:00.001-07:002013-05-19T08:22:29.406-07:00[Reporting Service] [Dynamic CRM] - Integrar con google maps (1/2)<p> </p> <p><strong>Introducción</strong> </p> <hr /> <p>La utilización de mapas para aportar valor en las aplicaciones es un aspecto cada vez mas requerido, las aplicaciones CRM están especializadas en trabar con cliente por lo que conocer su distribución aporta valor al usuario.</p> <p>En este caso uniremos tres tecnologías Dynamic CRM el cual nos aportara los datos de las cuentas, Reporting Service para el listado de información, y Google Maps para obtener la imagen del mapas que será incrustado en el reporte</p> <p>Por lo extenso del articulo se realizaran dos partes, esta primera donde se verán los pasos necesarios para la creación del reporte y su edición en el Visual Studio</p> <p>Una segunda parte se encargara de presentar como integrar la librería dll con el reporte y su posterior publicación en CRM.</p> <p> </p> <p><strong>Creación del reporte desde Dynamic CRM </strong> <hr /></p> <p>El primer paso será definir la estructura básica del reporte, para esta tarea nos ayudaremos con el el wizard que provee CRM.</p> <p>1- Creamos el nuevo reporte</p> <p><a href="http://lh6.ggpht.com/-QZ9_r83XIIo/UZjrZ5eOsAI/AAAAAAAAAv8/esJpqTYCyLc/s1600-h/image12.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh4.ggpht.com/-ryEamQncHGU/UZjre61wNOI/AAAAAAAAAwE/uxT9BzvA8oY/image_thumb8.png?imgmax=800" width="727" height="360" /></a></p> <p> </p> <p>2- Se define como entidad primaria al cliente</p> <p><a href="http://lh3.ggpht.com/-RJLvNOY6slc/UZjrgOluQBI/AAAAAAAAAwM/zMHl-pS3xLI/s1600-h/image19.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh5.ggpht.com/-o0JWOj2t1_w/UZjrqlgC_JI/AAAAAAAAAwU/Ob_97covEIQ/image_thumb13.png?imgmax=800" width="571" height="320" /></a></p> <p> </p> <p>3- Se definen los filtros si hace falta, en este caso solo listaremos los clientes de Buenos Aires</p> <p><a href="http://lh6.ggpht.com/-5IPl3KW4lnY/UZjrrpDrKmI/AAAAAAAAAwc/5jB-FVKDuMI/s1600-h/image48.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-3626WDjGEUs/UZjrusOPa3I/AAAAAAAAAwk/U3MNqnAEsVg/image_thumb32.png?imgmax=800" width="579" height="329" /></a></p> <p> </p> <p>4- Se definen las columnas del reporte</p> <p><a href="http://lh5.ggpht.com/-uAxH2uQdOgU/UZjrv2gxyvI/AAAAAAAAAws/Otv6BKLKTJU/s1600-h/image30.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh3.ggpht.com/-UfW7gwvDPCM/UZjr0oeMPVI/AAAAAAAAAw0/PZ58RmURJZY/image_thumb20.png?imgmax=800" width="574" height="343" /></a></p> <p> </p> <p>5- Se aceptan los cambios validando que la entidad asociada sea la cuenta</p> <p><a href="http://lh4.ggpht.com/-qQG6biuV1PU/UZjr30NQ7eI/AAAAAAAAAw8/rgNwC0Ify7E/s1600-h/image36.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh5.ggpht.com/--8QMumcJx98/UZjr7tkivbI/AAAAAAAAAxE/Z08KEOwISeo/image_thumb24.png?imgmax=800" width="457" height="457" /></a></p> <p> </p> <p>Estos pasos nos permitirán contar con una estructura básica del reporte que mas tarde usaremos para vincular con la librería encargada de generar la imagen del mapa.</p> <p>Ejecutamos el reporte para validar que este correcto</p> <p><a href="http://lh4.ggpht.com/-G0oA03GYuCo/UZjsBjLVh0I/AAAAAAAAAxM/8ohzw0S3sWw/s1600-h/SNAGHTML20981b197.png" target="_blank"><img style="display: inline" title="SNAGHTML20981b19" alt="SNAGHTML20981b19" src="http://lh6.ggpht.com/-mQhGsSMdz_w/UZjsHzpCBBI/AAAAAAAAAxU/1Or03uStacU/SNAGHTML20981b19_thumb4.png?imgmax=800" width="509" height="303" /></a></p> <p><a href="http://lh4.ggpht.com/-P3eHjhfNVjU/UZjsJMzLHbI/AAAAAAAAAxc/O4x_TqpFTpA/s1600-h/image57.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh6.ggpht.com/-br5HVL7HAV8/UZjsMUoMOqI/AAAAAAAAAxk/bCFiQbv0Tk0/image_thumb39.png?imgmax=800" width="604" height="386" /></a></p> <p> </p> <p><strong>Edición Reporte desde Visual Studio 2008 </strong> <hr /></p> <p>El siguiente paso requiere de Visual Studio 2008 con las tools de Business Intelligence las cuales se instalan al agregar el <a href="http://www.microsoft.com/en-us/download/details.aspx?id=1842" target="_blank">Sql Server 2008 Express Advanced Services</a>, este incluye el servicio de Reporting Service.</p> <p>En el Visual Studio deberíamos poder crear un proyecto como el siguiente:</p> <p><a href="http://lh6.ggpht.com/-Wm3z-kKQPtU/UZjsqmZD8JI/AAAAAAAAAxs/nBDYucK0Ig0/s1600-h/SNAGHTML2abc8dae6.png" target="_blank"><img style="display: inline" title="SNAGHTML2abc8dae" alt="SNAGHTML2abc8dae" src="http://lh5.ggpht.com/-0GVqX3hpDyU/UZjsyQLjCuI/AAAAAAAAAx0/bcttPaoXkjE/SNAGHTML2abc8dae_thumb3.png?imgmax=800" width="628" height="349" /></a></p> <p> </p> <p>En CRM ubicamos el reporte y lo editamos</p> <p><a href="http://lh6.ggpht.com/-tPxLNE_Zdss/UZjs10WmpjI/AAAAAAAAAx8/QvGKjzsiH1I/s1600-h/image66.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh4.ggpht.com/-JgKd-Z9oJVc/UZjs_ntpzoI/AAAAAAAAAyE/jXDoGsrKVtA/image_thumb44.png?imgmax=800" width="543" height="263" /></a></p> <p>Usamos la opción para descargar el reporte como archivo rdl</p> <p><a href="http://lh6.ggpht.com/-VJFZ8gnA10c/UZjtBcMgaZI/AAAAAAAAAyM/doKZk1hRjGQ/s1600-h/image70.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh5.ggpht.com/-eVx54iqun6U/UZjtMFg8JLI/AAAAAAAAAyU/VvXAALhcTyE/image_thumb48.png?imgmax=800" width="457" height="395" /></a></p> <p>El archivo descargado lo deberíamos ubicar en la carpeta del proyecto que creamos con el Visual Studio</p> <p><a href="http://lh6.ggpht.com/-T5hWEdhQSJE/UZjtNpvfpHI/AAAAAAAAAyc/qAMWUUAODfo/s1600-h/image77.png" target="_blank"><img style="display: inline" title="image" alt="image" src="http://lh3.ggpht.com/-THSYlZBdfrQ/UZjt13E7aAI/AAAAAAAAAyo/yEyeV6uGg0E/image_thumb53.png?imgmax=800" width="464" height="239" /></a></p> <p>Para luego agregarlo al proyecto</p> <p><a href="http://lh6.ggpht.com/-gZXVhrlAVHw/UZjt4RnEcJI/AAAAAAAAAyw/ffSfvfMR5xc/s1600-h/SNAGHTML2acbd8d27.png" target="_blank"><img style="display: inline" title="SNAGHTML2acbd8d2" alt="SNAGHTML2acbd8d2" src="http://lh4.ggpht.com/-zsXMCaRVUNU/UZjt8lpxwLI/AAAAAAAAAy4/D4tGwteACis/SNAGHTML2acbd8d2_thumb4.png?imgmax=800" width="609" height="275" /></a></p> <p> </p> <p>Si editamos el reporte veremos que se conserva el diseño, así como la información de conexión y campos disponibles</p> <p><a href="http://lh4.ggpht.com/-IEANV-nzJsI/UZjuD3sk82I/AAAAAAAAAzA/V6v6-IrKpvU/s1600-h/SNAGHTML2acf227e7.png" target="_blank"><img style="display: inline" title="SNAGHTML2acf227e" alt="SNAGHTML2acf227e" src="http://lh3.ggpht.com/-2nMoVJ2VjOE/UZjuMnKsozI/AAAAAAAAAzI/M78PRCoqDAY/SNAGHTML2acf227e_thumb4.png?imgmax=800" width="693" height="253" /></a></p> <p> </p> <p>Es aquí donde vamos a definir la invocación a la librería que devolverá la imagen con el mapa de google representando la ubicación de los cuentas.</p> <p>En el siguiente articulo veremos como crear el componente y vincularlo al reporte.</p> Anonymoushttp://www.blogger.com/profile/01910634216468650091noreply@blogger.com3