miércoles, 25 de marzo de 2020

Cómo hacer un Join Linq con multiples DataContext

Muchas veces tenemos que hacer un Query con Linq que nos une a 2 DataContext Entity Framework, pero podemos encontrar un problema al momento de ejecutarlo y la solución al problema debe tener en cuenta que no se degrade el rendimiento del Query ante la base de datos.

PROBLEMA : Al hacer un Query donde incluyo 2 DataContext obtengo el siguiene error :

"La expresión LINQ especificada contiene referencias a consultas que están asociadas a contextos diferentes"
"The specified LINQ expression contains references to queries that are associated with different contexts."

El siguiente es el Query que se ejecuta uniendo 2 DataContext :

protected void Button1_Click(object sender, EventArgs e)
{
    try
    {
        NCSCEntities db = new NCSCEntities();   //DataContext 1
        NCSCViewEntities dbViews = new NCSCViewEntities();  //DataContext 2

        var query = (from p in db.Requests
                                from v in dbViews.V_Union1
                                where p.Id == 22495 && v.IdRequest == 12493 && p.Id == v.IdRequest                                  orderby v.Request2
                                select new { Id = p.Id, IdCompleTravellers = p.Comments });

        //Se va a ejecutar todos los registros de dbViews.V_Union1 como si hiciera un Select * from V_Union1
        //lo cual es una mala decision de Rendimiento.
        Console.WriteLine(query.Count());

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}


SOLUCION 1 : La forma más rápida de corregir el problema es colocando el método .AsEnumerable() asi :

protected void Button1_Click(object sender, EventArgs e)
{
    try
    {
        NCSCEntities db = new NCSCEntities();   //DataContext 1
        NCSCViewEntities dbViews = new NCSCViewEntities();  //DataContext 2

        var query = (from p in db.Requests.AsEnumerable()
                                from v in dbViews.V_Union1
                                where p.Id == 22495 && v.IdRequest == 12493 && p.Id == v.IdRequest                                  orderby v.Request2
                                select new { Id = p.Id, IdCompleTravellers = p.Comments });

        //Se va a ejecutar todos los registros de dbViews.V_Union1 como si hiciera un Select * from V_Union1
        //lo cual es una mala decision de Rendimiento.
        Console.WriteLine(query.Count());

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

La anterior solución funciona, pero trae un gran problema de rendimiento, cuando vaya a ejecutar el resultado del Query ejemplo : Console.WriteLine(query.Count()) se van a ejecutar todos los registros de dbViews.V_Union1 como si hiciera un Select * from V_Union_TR_PO, aun cuando tenga el filtro : v.IdRequest == 12493, lo cual es una mala decision de Rendimiento. Este comportamiento es causado por haber colocado el método .AsEnumerable()

Si activa el SQL Analizer se dará cuenta que se hace un Query a TODOS los registros :



SOLUCION 2 (Versión mejorada ): Lo mejor es separar los Queries, hacer de manera individual  el Query de cada DataContext y y luego si hacer un JOIN para obtener el resultado buscado. En este caso solo se ejecuta ante Sql Server 2 Queries que devuelven 1 o 2 registros, de acuerdo a lo establecido en la clausula Where, lo cual aumenta enormemente el rendimiento. Además para temas de mantenimiento en el codigo se me hace mas facil.

NOTA : Si al hacer el Join de los 2 Queries llega a tener algun mensaje de error, por unir los 2 DataContext, puede utilizar el método .ToList() en cada Query individual.

protected void Button2_Click(object sender, EventArgs e)
{
    try
    {
        NCSCEntities db = new NCSCEntities();
        NCSCViewEntities dbViews = new NCSCViewEntities();

        var query1 = (from p in db.Requests
                        where p.Id == 22495
                        select new { Id = p.Id, Commentary = p.CommentTravel }); //.ToList();

        var query2 = (from v in dbViews.V_Union1
                        where v.IdRequest == 12493
                        select new { Id = v.IdRequest, Commentary = v.ComentaryTravelStatus }); //.ToList();


        var listTravellers = (from p in query1
                                from v in query1
                                where p.Id == v.Id
                                select new { Id = p.Id, IdCompleTravellers = p.Commentary }).ToList();

        Console.WriteLine(listTravellers.Count);

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
} Leer más...>>

martes, 24 de marzo de 2020

Cómo utilizar SqlDatasource y la propiedad CacheKeyDependency para manejar memoria Caché en Asp.net

PROBLEMA : A veces tenemos un control SqlDataSource al cual le queremos colocar un tiempo de cache largo, ejemplo de 10 horas, supongamos que este control contiene un Query de las Areas de la empresa, pero puede que haya algo en nuestra aplicación asp.net que requiera actualizar la cache del SqlDataSoruce antes de las 10 horas, pudo haber un cambio en las Areas y necesitamos que se vuelva a ejecutar el Query ante la base de datos. Cómo manejar este uso de Caché?

SOLUCION : El control SqlDataSource tiene la propiedad CacheKeyDependency, la cual permite colocar un nombre de memoria Cache, al cual por codígo fuente se puede resetear desde cualquier pagina Web de mi proyecto asi :

--Parte I : En tiempo de Diseño establecer las Propiedades del SqlDataSource

asp:SqlDataSource ID="SqlDataSource1" runat="server"
CacheDuration="600" CacheKeyDependency="CacheSqlAreas" EnableCaching="True"
ConnectionString="<%$ ConnectionStrings:CadConexSQL %>" 
SelectCommand="SELECT * FROM [Area]"


--Parte II. Código fuente de pagina Web.
const string CACHE_PROVIDERS = "CacheSqlAreas";

protected void Page_Load(object sender, EventArgs e)
{
    //IMPORTANTE Establecer la Cache si esta no existe.
    if (Cache[CACHE_PROVIDERS] == null)
        Cache[CACHE_PROVIDERS] = DateTime.Now;

}


//Al ser Cache[CACHE_PROVIDERS] un objeto Global puede ser utilizado en cualquier parte del proyecto, para
//Borrar la memoria Cache que utiliza el SqlDataSource. Esto hará que al volver hacer DataBind()
//del Control que lo utilice se vuelva a hacer el Query en la base de datos.
protected void BtonDeleteCache_Click(object sender, EventArgs e)

   //Borrar la Caché del SqlDataSource.
    Cache[CACHE_PROVIDERS] = DateTime.Now;
}
Leer más...>>

miércoles, 4 de marzo de 2020

Cómo exportar a .pdf un Reporte .rdlc - Reporting Service de Visual Studio.

Muchas veces necesitamos en Visual Studio, para un proyecto Asp.net, por código fuente exportar a .pdf o .xls o .doc. Algunos de nuestros reportes .rdlc

PROBLEMA : Cual es el código fuente para hacer dicha exportación ?

SOLUCION : Debe haber creado previamente su Informe .rdlc. Para este ejemplo vamos a asumir que tiene el informe Report1.rdlc cuya origen de datos es una lista de objetos sencillos. El siguiente es el código fuente escrito en C# que exporta el reporte a la ruta : D:\auxil\reporte1.pdf.

Este código fuente puede ajustarse para exportar cualquier informe .rdlc


private void CreateReportPdf()
{
    //Origen de Datos que pobla el Reporte, en este caso es una lista de objetos sencillos, pero puede ser un Dataset o un DataTable
    List<CItem2> list1 = new List<CItem2>{
        new CItem2{ Id= 1, Description ="Amarillo"},
        new CItem2{ Id= 2, Description ="Azul"}, 
        new CItem2{ Id= 3, Description ="Rojo"},


    };

    try
    {
        //1. Cargar fisicamente el reporte .rdlc
        LocalReport report = new LocalReport();
        report.ReportPath = Server.MapPath("~/Report1.rdlc");

        //2. Establecer el Origen de datos.
        ReportDataSource rds = new ReportDataSource();
        rds.Name = "DataSet1";      //Este es el nombre del DataSet que estableció en tiempo de Diseño al .rdlc

        rds.Value = list1;         //Origen de datos : DataTable, List, etc.
        report.DataSources.Add(rds);


        //3. Establecer la ruta Destino donde se va a Guardar el .pdf generado

        string pathFilePdf = @"D:\auxil\reporte1.pdf";

        //4. Escoja cualquier formato al que quiera Exportar su reporte.

 //Byte[] mybytes = report.Render("Excel");       
 //Byte[] mybytes = report.Render("WORD");

        Byte[] mybytes = report.Render("PDF"); //for exporting to PDF
        using (FileStream fs = File.Create(pathFilePdf))  //Report1.doc
        {
            fs.Write(mybytes, 0, mybytes.Length);  //Exportar el Reporte
        }

    }
    catch (Exception ex)
    {
        Console.writeline(ex);
    }
}
Leer más...>>