viernes, 25 de agosto de 2017

Utilizar .Net Core MVC con MySQL sin Entity Framework y con consultas SQL simples

Hola dejo aquí el cómo podemos utilizar el .Net Core MVC pero pasando del Entity Framework para las conexiones a la DB y teniendo así un poco más de flexibilidad, que aunque no es quizás, tan puritano como algunos querrían, suele dar más versatilidad, y, en algunos proyectos suele ser más efectivo; sobre todo si tenemos programadores que saben de bases de datos y de SQL.

Premisas:

  • Tengo un servidor MySql instalado en local con un esquema test1 y una tabla data, que contienen dos campos id (entero sin signo, autoincremental) y dato (varchar de 45), usuario test1 con clave test1.
  • Voy a utilizar Visual Studio Community 2017 para todo el proceso, ya que está lo aprovechamos.

Creamos proyecto .Net Core MVC





Añadimos paquete Nuget del conector MySQL 

Botón derecho encima de la solución -> Administrar paquetes NuGet para la solución
Pestaña examinar Buscar Mysql

Presionar Instalar

Leemos y aceptamos si estamos de acuerdo con lo que pregunta (deberíamos):



Et voilà

Nota importante: la versión estable instalada en el momento de hacer este documento no funcionó bien y tuve que marcar la casilla de "Incluir versiones preliminares" y actualizar a la 6.10.3rc, posiblemente cuando leáis esto ya la versión que esté subida como estable será buena, por lo tanto he preferido mantener el artículo con la instalación de estable.



Añadimos el ConnectionString al fichero de configuración:

"ConnectionStrings": {
    "DefaultConnection": "server=localhost;port=3306;database=test1;user=test1;password=test1"
  }

Modelo de datos

Vamos a crear el modelo de datos, para ello creamos una nueva clase dentro de la carpeta Models que defina nuestro modelo de datos
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace test1.Models
{
    public class Dato
    {
        private string _dato;
        public int id { get; set; }
        public string dato
        {
            get { return _dato; }
            set
            {
                if (value.Length < 45)
                    _dato = value.Substring(0, 45);
                else
                    _dato = value;
            }
        }
      
    }
}


Nota importante: en el código vemos que el dato del id es tipo int, mientras que en la base de datos es uint, esto lo he puesto así, porque para el paso siguiente lo vamos a necesitar así, sino falla el asistente, pero luego hay que acordarse de cambiarlo de nuevo a uint porque no estamos usando EF.

Controlador, contexto y vistas


Vamos a añadir el controlador tal y como si estuviésemos usando Entity Framework, porque esto nos va a ahorrar escribir unas cuantas líneas.

Por lo tanto vamos a presionar con el botón derecho encima de la carpeta Controllers y darle a la opción Agregar -> Controlador y seleccionamos "Controlador MVC con ... Entity Framework"

Pulsamos "Agregar"
Completamos los datos, la clase modelo está clara en este caso es nuestra clase Dato, al seleccionarla nos pone un nombre de controlador, en mi caso lo he modificado para que se llame DatoControler   en clase de contexto pulsamos el "+" 
Elegimos el nombre de nuestro contexto de datos, en este caso le dejo el que está y pulsamos Agregar
Volvemos a pulsar Agregar

Bien si todo va bien, vemos que tendríamos, según el log de salida:
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\francisco.counago\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
Added DbContext : '\Data\test1Context.cs'
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
      Entity Framework Core 2.0.0-rtm-26452 initialized 'test1Context' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Added Controller : '\Controllers\DatoController.cs'.
Added View : \Views\Dato\Create.cshtml
Added View : \Views\Dato\Edit.cshtml
Added View : \Views\Dato\Details.cshtml
Added View : \Views\Dato\Delete.cshtml
Added View : \Views\Dato\Index.cshtml


Es decir el esqueleto de todo lo que necesitamos. 

Nota: cambiar el int a uint de la clase dato, porque ya podemos hacerlo.

Además si abrimos el Startup.cs veremos que ha añadido una línea para registrar nuestro contexto

¿Cuál es el problema?, nosotros no vamos a usar un contexto al uso por lo tanto debemos cambiar, algunas cosas, primero, nuestro contexto "text1Context" no viene de una clase DbContext, por lo tanto vamos a eliminar está herencia, cambiamos:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace test1.Models
{
    public class test1Context : DbContext
    {
        public test1Context (DbContextOptions options)
            : base(options)
        {
        }

        public DbSet Dato { get; set; }
    }
}

Por esto, esqueleto básico de nuestro Contexto

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace test1.Models
{
    public class test1Context
    {
        public test1Context (string pConexion)            
        {
        }        
    }
}

Por lo tanto como tampoco nos podemos añadir como un services.AddDbContext sino como un nuevo servicio, para ello cambiamos en el Startup.cs:
esto

public void ConfigureServices(IServiceCollection services)
{
            services.AddMvc();

            services.AddDbContext(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("test1Context")));
          
}

por esto
 public void ConfigureServices(IServiceCollection services)
 {
            services.AddMvc();
            services.Add(new ServiceDescriptor(typeof(test1Context), new test1Context(Configuration.GetConnectionString("DefaultConnection"))));            
 }

Con esto nos hemos desecho del DbContext del EF, realmente todo esto puede hacerse a mano sin el asistente del EF, pero he preferido hacerlo así, para que se vea que generamos lo mismo que con el EF.
Si queremos aprovechar lo máximo las vistas generadas para el EF, sólo tenemos que hacer métodos dentro de nuestro contexto que llamaremos desde el controlador.

No me voy a parar en todos los detalles (se puede ver en el código), básicamente lo que voy a hacer es cambiar este controlador

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using test1.Models;

namespace test1.Controllers
{
    public class DatoController : Controller
    {
        private readonly test1Context _context;

        public DatoController(test1Context context)
        {
            _context = context;
        }

        // GET: Dato
        public async Task Index()
        {
            return View(await _context.Dato.ToListAsync());
        }

        // GET: Dato/Details/5
        public async Task Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var dato = await _context.Dato
                .SingleOrDefaultAsync(m => m.id == id);
            if (dato == null)
            {
                return NotFound();
            }

            return View(dato);
        }

        // GET: Dato/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: Dato/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task Create([Bind("id,dato")] Dato dato)
        {
            if (ModelState.IsValid)
            {
                _context.Add(dato);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(dato);
        }

        // GET: Dato/Edit/5
        public async Task Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var dato = await _context.Dato.SingleOrDefaultAsync(m => m.id == id);
            if (dato == null)
            {
                return NotFound();
            }
            return View(dato);
        }

        // POST: Dato/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task Edit(int id, [Bind("id,dato")] Dato dato)
        {
            if (id != dato.id)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(dato);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!DatoExists(dato.id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            return View(dato);
        }

        // GET: Dato/Delete/5
        public async Task Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var dato = await _context.Dato
                .SingleOrDefaultAsync(m =>; m.id == id);
            if (dato == null)
            {
                return NotFound();
            }

            return View(dato);
        }

        // POST: Dato/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task DeleteConfirmed(int id)
        {
            var dato = await _context.Dato.SingleOrDefaultAsync(m => m.id == id);
            _context.Dato.Remove(dato);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

        private bool DatoExists(int id)
        {
            return _context.Dato.Any(e => e.id == id);
        }
    }
}


por este
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using test1.Models;

namespace test1.Controllers
{
    public class DatoController : Controller
    {
        private readonly test1Context _context;

        public DatoController(test1Context context)
        {
            _context = context;
        }

        // GET: Dato
        public  IActionResult Index()
        {
            return View(_context.Listado());
        }

        //// GET: Dato/Details/5
        public IActionResult Details(uint? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            Dato datoAux = _context.dame(id.Value);
            if (datoAux == null)
            {
                return NotFound();
            }
            return View(datoAux);
        }

        // GET: Dato/Create
        public IActionResult Create()
        {
            return View();
        }

        // POST: Dato/Create
        [HttpPost]
        public IActionResult Create(String dato)
        {
            if(dato==null)
            {
                return RedirectToAction(nameof(Create));
            }
            String strDato = dato;
            if(_context.Add(strDato))
            {
                return RedirectToAction(nameof(Index));
            }
            return View(new Dato { dato = strDato });
        }

        //// GET: Dato/Edit/5
        public IActionResult Edit(uint? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Dato datoAux = _context.dame(id.Value);
            if (datoAux == null)
          {
               return NotFound();
          }
            return View(datoAux);
        }

        //// POST: Dato/Edit/5
        [HttpPost]    
        public  IActionResult Edit(uint? id, string dato)
        {
            if (id ==null)
            {
                return NotFound();
            }
            uint uiId = id.Value;
            if (dato == null)
            {
                return RedirectToAction(nameof(Edit), new { id =uiId });
            }
            String strDato = dato;
            if (_context.Update(uiId,strDato))
            {
                return RedirectToAction(nameof(Index));
            }
            return View(new Dato { dato = strDato, id=uiId });
        }

        //// GET: Dato/Delete/5
        public IActionResult Delete(uint? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            Dato datoAux = _context.dame(id.Value);
            if (datoAux == null)
            {
                return NotFound();
            }
            return View(datoAux);
        }

        //// POST: Dato/Delete/5
        [HttpPost, ActionName("Delete")]

        public IActionResult DeleteConfirmed(uint? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            uint uiId = id.Value;
            if (_context.Delete(uiId))
            {
                return RedirectToAction(nameof(Index));
            }
            return RedirectToAction(nameof(Delete),new { id = uiId });
        }       
    }
}




y añadir a nuestro contexto las funciones necesarias, quedando de esta forma





using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;


namespace test1.Models
{
    public class test1Context
    {
        private string strCnnString;
        public test1Context (string pstrConexion)            
        {
            strCnnString = pstrConexion;        
        }
        private List hazConsulta(string pstrConsulta)
        {
            List lstDato = new List();
            using (MySqlConnection cnn = new MySqlConnection(strCnnString))
            {
                cnn.Open();
                using (MySqlDataReader drAux = (new MySqlCommand(pstrConsulta, cnn).ExecuteReader()))
                {
                    while (drAux.Read())
                    {
                        lstDato.Add(new Dato()
                        {
                            id = drAux.GetUInt32("id"),
                            dato = drAux.GetString("dato")
                        });
                    }
                }
            }
            return lstDato;
        }
        private int ejecutaSQL(string pstrConsulta)
        {
            int iFilas = 0;
            using (MySqlConnection cnn = new MySqlConnection(strCnnString))
            {
                cnn.Open();
                iFilas = (new MySqlCommand(pstrConsulta, cnn).ExecuteNonQuery());
            }
            return iFilas;
        }
        public bool  Add(string pDato)
        {
  
            if (pDato==null || pDato.Trim().Length < 1)
                return false;
            try
            {
                return (ejecutaSQL($"insert into dato (dato) values (\"{pDato}\")") > 0) ? true : false;                
            }catch
            {
                return false;
            }
        }
        public bool Update(uint puiId,string pDato)
        {

            if (pDato == null || pDato.Trim().Length < 1)
                return false;
            try
            {
                return (ejecutaSQL($"update dato set dato= \"{pDato}\" where id={puiId}") > 0) ? true : false;
            }
            catch
            {
                return false;
            }
        }
        public bool Delete(uint puiId)
        {
            
            try
            {
                return (ejecutaSQL($"delete from dato where id={puiId}") > 0) ? true : false;
            }
            catch
            {
                return false;
            }
        }
        public List Listado()
        {

            return hazConsulta("select * from dato");
        }
        public Dato dame(uint puiId)
        {
            try
            {
                return hazConsulta($"select * from dato where id={puiId}")[0];
            }
            catch
            {
                return null;
            }              
        }


    }
}




Con esto podríamos acceder perfectamente a todas las funcionalidades "autogeneradas" pero sin EF, teniendo de esta forma mayor control sobre lo que queremos hacer, ojo, mayor control implica mayor responsabilidad a la hora de controlar los datos que si lo hacemos con el EF y un mayor control de errores, pero estas cosas están fuera del ámbito de este artículo. Podemos darle a Ejecutar en IISExpress y vemos nuestro resultado a través de URL/dato







Como podemos concluir, podemos prescindir del EF para hacer las cosas y aprovechar ciertas ventajas que nos proporciona el Visual Studio como si estuviésemos usándolo. 



No hay comentarios:

Publicar un comentario

L2TP Ipsec Windows to Mikrotik eror 789

 Add this to registry REG ADD HKLM\SYSTEM\CurrentControlSet\Services\PolicyAgent /v AssumeUDPEncapsulationContextOnSendRule /t REG_DWORD /d...