Insertar imagen en base de datos Access

_Melk0r_

Hola, gente. Estoy desarrollando una aplicación para mi proyecto fin de carrera con Visual Studio y me he quedado totalmente estancado en algo que en teoría no debería ser tan difícil, así que vengo aquí a ver si me podéis echar una mano :) Voy a intentar poneros en situación lo mejor que pueda:

La aplicación tiene como objetivo principal la gestión de empleados de una pequeña empresa. En su parte más básica, se trata de insertar y actualizar la información de los empleados.

He creado una clase "Empleados", con su área de datos y funciones observadoras y modificadoras. Es decir, en el área de datos podríamos encontrar cosas como:
- double sueldo;

  • string poblacion;
    y en los metodos, funciones como:
    - public void CambiaSueldo(double nuevosueldo) { sueldo = nuevosueldo }
  • public void CambiaPoblacion(string nuevapoblacion) { poblacion = nuevapoblacion }
  • public double VerSueldo () { return sueldo }
  • public string VerPoblacion () { return poblacion }[/b]

Todo muy standard.

También, tengo creada una clase "BasedeDatos", donde estan los métodos para insertar o actualizar la información de dichos empleados:

//Método que nos permite conectarnos a la base de datos

  • public OleDbConnection ConexBD() {
    string connection = ("Provider=Microsoft.ACE.OleDB.12.0; Data Source=.\BasedeDatos.accdb" );
    OleDbConnection conex = null;

        try { conex = new OleDbConnection(connection); }
        catch (Exception ex) { MessageBox.Show("No se ha podido establecer conexión con la base de datos: \n" + ex); }
    
        return conex;
    }[/i]

Así, con los ejemplos anteriores de Sueldo y Poblacion, tendríamos algo como esto dentro del método para insertar un empleado en la base de datos (dentro de la clase "BasedeDatos" ):

public void InsertaEmpleado(Empleado e) {
OleDbConnection conexion = ConexBD(); //Conectamos con la base de datos
OleDbCommand inserta = null;
string sqlinsert = "INSERT INTO Empleados (Sueldo, Poblacion) VALUES (@sueldo, @poblacion)";
inserta = new OleDbCommand(sqlinsert, conexion);

inserta.Parameters.Add(new OleDbParameter("@poblacion", OleDbType.VarChar));
inserta.Parameters["@poblacion"].Value = e.VerPoblacion();

inserta.Parameters.Add(new OleDbParameter("@sueldo", OleDbType.Double));
inserta.Parameters["@sueldo"].Value = e.VerSueldo();

}

He resaltado en negrita lo verdaderamente importante de mi consulta. Y es que además de esos datos, hay muchísimos más, de tipo string, double, int o incluso date. Y no he tenido mayores problemas con ninguno, excepto cuando intento guardar un archivo de imagen en la base de datos b[/b].

Lo he intentado declarando una variable fotografia en "Empleados" como object fotografia; (porque en teoría es un objecto OLE) y añadiendo este código en "BasedeDatos" :

inserta.Parameters.Add(new OleDbParameter("@foto", OleDbType.VarBinary));
inserta.Parameters["@foto"].Value = d.VerFotografia();

pero cuando voy a insertar me da este error: "El objeto debe implementar IConvertible".

No sé si es que tengo que declarar la variable foto como otro tipo, o si directamente no se pueden guardar imágenes en la base de datos y hay que guardar el enlace o algo así. Por eso me preguntaba si alguien sabría que puedo hacer para insertar una foto manteniendo un poco el orden en el código. Es decir, implementarlo dentro de la clase "basededatos" y no por ejemplo desde el form donde selecciono la imagen.

Sé que puede resultar un poco lioso, espero haberme explicado lo mejor posible.

Un saludo y muchas gracias por adelantado. Toda ayuda es bienvenida.

willy_chaos

Si no he entendido mal, estas guardando el codigo binario del archivo fotografia en la base de datos?

Lo que suele hacerse en estos casos es guardar la ruta o incluso guardar solo el nombre de la imagen y setear la ruta desde el archivo config.properties de tu aplicacion. Esto evitará que tu base de datos crezca innecesariamente ya que esas imagenes las puedes guardar en un servidor web y que se descarguen a traves de la conexion (si es una aplicación en local, pues que tire de acceso a la carpeta local).

Piensa que de esta forma la base de datos te devuelve un pequeño string en vez de un codigo binario gigantesco. El resultado es el mismo y la base de datos con muchas imagenes ocupa poco en comparacion con lo que te ocuparia, ademas de no importar cuanta resolucion tiene la imagen.

Esto te solventaria:

  • El servidor de base de datos no necesita gran hardware
  • El servidor de base de datos no necesita mucho espacio
  • El servidor de imagenes puede ser diferente al servidor de la base de datos
  • El trabajo de descarga de la imagen se la come el cliente con el servidor de imagenes, el de la base de datos una vez ha dado el nombre de la imagen, ni se entera de lo que haces.
  • Te facilita la implementacion, puesto que estas guardando un string en vez de un objeto.

Por ejemplo para el usuario pepe, harias una query estilo

SELECT foto FROM usuarios_app WHERE login = 'pepe';

Te devolveria un string "04_pepe.jpg".

Luego irias a tu archivo config properties donde tendrias definido una propertie estilo

path_images=http://www.miservidorimagenes.com/imgs/

y simplemente realizarias un

path_images+"04_pepe.jpg" para descargarla y luego pintarla.

1 respuesta
_Melk0r_

#2

En primer lugar, muchísimas gracias por contestar :)

No, mi intención no es guardar el código binario de la imagen. La verdad es que puse lo de OleDbType.VarBinary porque lo encontré por internet y no era capaz de encontrar en OleDbType otro tipo de datos que reflejara correctamente un archivo de imagen (ya sabes, double para double, varchar para string, etc...). Tampoco sé si estoy definiendo correctamente la variable fotografía en la clase "Empleados". No sé si es un object fotografía, o si debo guardarlo como otro tipo...

En realidad, tu idea de guardar la ruta me parece genial (eso sí, es una aplicación local). Es solo que no sé como hacerlo. Si te he entendido bien, lo que debería hacer sería declarar la variable fotografía en la clase "empleados" como un string, y en dicho string guardar la ruta de la imagen. ¿Es así?

Y una vez guardada, ¿cómo consigo cargarla y mostrarla en pantalla al cargar un formulario? Acabo de ver que los PictureBox tienen una propiedad llamada ImageLocation que, literalmente, "obtiene o estable la ruta o dirección URL de la imagen que se va a mostrar en el control. ¿Voy por buen camino?

Muchas gracias!

Edit: Acabo de ver, que has actualizado el mensaje. Como te digo, en principio es para una aplicación local, osea que tendría que guardar las imágenes en una carpeta, dentro de la carpeta de la aplicación, a su vez, supongo.

willy_chaos

Exacto, a la hora de guardar le darías el nombre a la imagen, la pondrías en la ruta indicada por el path que te he comentado y harías el insert/update de la base de datos.

Lo del path es más que nada xq si más adelante cambias el nombre de la carpeta o reestructuras las carpetas de la app, sólo cambiando la ruta en el archivo configuración.properties ya estaria. No haría falta hacer updates en la db.

1 respuesta
_Melk0r_

#4 Ya casi lo he conseguido, solo me falta guardar la imagen que cargo desde la aplicación en una carpeta local, que creo que es justo de lo que hablas del path en config properties. Esto es lo que tengo, de momento:

private void PBFoto_Click(object sender, EventArgs e)
{
try
{
openFileDialog1.Title = "Open Picture";
openFileDialog1.FileName = ".jpg";
openFileDialog1.Filter = "All Files |.";
openFileDialog1.ShowDialog();
PBFoto.Load(openFileDialog1.FileName);
}
catch (Exception ex)
{
// Do Nothing End Try End Try
}
}

Con esta funcion consigo cargar la foto en el formulario una vez que pincho en él, y después, con un:
string foto = PBFoto.ImageLocation;
d1.CambiaFotografia(foto);

consigo que en la base de datos aparezca efectivamente la ruta de la imagen seleccionada (Por ejemplo C:\Users\Administrador\Desktop\Imágenes PFC\juan.jpg)

Ahora, lo que me falta por hacer, si es posible, es que esa imagen se guarde automáticamente dentro de una carpeta, a su vez dentro de la carpeta de la aplicación. Entiendo que tiene que ver con lo que me sugerías de config properties, pero le he estado echando un vistazo y no sé como hacer para cambiar la ruta de la fotografía.

Obviamente esto se solucionaría si todas las fotos se importaran desde una carpeta concreta ya establecida a tal efecto dentro de la carpeta de la aplicación, pero sería genial si pudiera cargar la foto desde cualquier lugar y hacer una copia o simplemente moverla a la carpeta deseada.

¿Se te ocurre como puedo hacerlo?

Una vez más, muchísimas gracias. Me has ayudado mucho :)

1 respuesta
willy_chaos

Mira justo es algo que estoy tocando yo en mi trabajo, aunque en mi caso no se trata de una app local, si no la web de los estudiantes de la universidad.

Para mover la imagen he hecho uso de

if ( rowsDeleted == 1 ) {
	// Renombramos el fichero
	File old_file = new File(funcions_globals.retornaPropietats().getProperty("midirectoriofotos")+filename);
	File new_file = new File(funcions_globals.retornaPropietats().getProperty("midirectoriofotos")+filename+"_backup_"+timestamp);
	if ( old_file.exists() ) {
		old_file.renameTo(new_file);
	}
}

compruebo que haya podido hacer un delete de la base de datos y si ha sido asi, entonces renombro el archivo de foto a un foto_backup_timestamp (para tener un backup por si la lian)

Lo de la ruta, te recomiendo lo del fichero .properties porque luego es mas facil cambiar la ruta sin tener que recompilar el codigo de la apliccacion.

Crea un fichero llamado "comoquieras.properties" donde le indicas un nombre de variable = valor.

Luego carga la informacion de ese fichero en el programa Java y recuperalo desde el metodo donde lo necesitas (por ejemplo en mi caso, veras la llamada a retornaPropietats y el nombre de esa variable en el fichero .properties ---> midirectoriofotos).

Luego simplemente aplicas el Files.move
https://docs.oracle.com/javase/tutorial/essential/io/move.html

Y por ultimo te aseguras de que se ha copiado bien haciendo un file exists de la nueva ruta.

En tu caso como ya tienes la ruta actual, no hace falta que le metas ahi el properties, solo en el nuevo nombre, para poder pasarselo al source. Ejemplo

Files.move(mirutaActualFichero,getProperties("midirectoriofotos")+filename,REPLACE_EXISTING);

Ojo que el Files.move puede darte una exception (permisos, carpeta no existente...). Yo en mi caso no lo hago ,porque nos interesa que si hay algun fallo, no sea ocultado (se nos autoenvia un mail).

1 respuesta
Camperito

Mejor, transforma la imagen a BASE64, que no deja de ser una cadena de texto, y listo

willy_chaos

#5 Te he enviado un mp con codigo de una app que tengo hecha.

_Melk0r_

#6 otra vez más, muchísimas gracias por contestar :)

He estado ayer y hoy dándole vueltas a todo lo que me has pasado y me he estado haciendo un lío tremendo xD. Primero porque estoy escribiendo el código en c# en vez de en java, y aunque sé que hay conversores de código sigue siendo un poco líoso. Después porque, como te decía, las fotos las quería guardar de forma local en la carpeta de la aplicación, en vez de en web. Además, y esto es lo más importante, no soy un buen programador :( y a poco que cambie la cosa me vuelvo loco xD

Sin embargo, después de mirar bien todo lo que me pasaste y de buscar un poco más por internet conseguí POR FIN lo que quería :D, así que comparto aquí el código por si a alguien (o a ti mismo) le pudiera servir.

Lo primero que hago es cargar la foto en cuanto se selecciona el PictureBox, con este código:

  [b]  string rutafoto;
    string nombrefoto;
    string rutasinfile;

    private void PBFoto_Click(object sender, EventArgs e)
    {
        openFileDialog1.FileName = "";
        openFileDialog1.Title = "Images";
        openFileDialog1.Filter = "JPG Image(*.jpg)|*.jpg|BMP Image(*.bmp)|*.bmp";
        openFileDialog1.ShowDialog();
        PBFoto.Load(openFileDialog1.FileName);
        if (openFileDialog1.FileName.ToString() != "" )
          {
            string rutaimagen = openFileDialog1.FileName.ToString();
            string nombreimagen = rutaimagen.ToString();
            nombreimagen = nombreimagen.Substring(nombreimagen.LastIndexOf("\\" ));
            nombreimagen = nombreimagen.Remove(0, 1);

            rutasinfile = Path.GetDirectoryName(rutaimagen);
            rutafoto = rutaimagen;
            nombrefoto = nombreimagen;
          }

}
[/b]
con ese trozo de código cargo la foto y obtengo la ruta completa, el nombre de la foto, y la ruta de la carpeta en la que se encuentra. Luego, añado este código que encontré en internet:

        [i]   //foto (hace una copia de la foto escogida y la guarda en la carpeta bin\debug\Imagenes)[/i]
       [b]      string fileName = nombrefoto;
            string sourcePath = rutasinfile;
            string targetPath = (".\\Imagenes" );

            string sourceFile = System.IO.Path.Combine(sourcePath, fileName);
            string destFile = System.IO.Path.Combine(targetPath, fileName);
 
            if (!System.IO.Directory.Exists(targetPath))
            {
                System.IO.Directory.CreateDirectory(targetPath);
            }

[/b]
// To copy a file to another location and
// overwrite the destination file if it already exists.

System.IO.File.Copy(sourceFile, destFile, true);

y ya está, solo queda hacer:

string foto = destFile;
d1.CambiaFotografia(foto);

y tras insertarla mediante el método correspondiente en "BasedeDatos", ya tengo una copia de la imagen original dentro de la carpeta Debug\Imagenes (tengo que ver como queda cuando haga el Release), y puedo guardar esa ruta como un string en la base de datos como me sugeriste en tu primera respuesta :)

Así que, como te digo, aunque lo último que me has pasado no lo haya sabido utilizar, te agradezco muchísimo que te hayas tomado tantas molestias en ayudarme.

Muchísimas gracias, de corazón :)

willy_chaos

Jajajaj de nada hombre, aquí estamos para ayudar. Yo muchas veces también me he quedado encallado en ciertas cosas y si no fuera por los foros ... XD suerte con el TFC si es posible cuando lo tengas terminado podrías pasarme el documento memoria :P.

Yo acabó este año aunque el TFC lo dejó pal siguiente junto con el master y aún no se que cojines hacer xd

Usuarios habituales

  • willy_chaos
  • _Melk0r_
  • Camperito