【问题标题】:How to manage multiple Winform instances with the same SQL Server 2012 connection?如何管理具有相同 SQL Server 2012 连接的多个 Winform 实例?
【发布时间】:2014-07-01 13:49:54
【问题描述】:

我有一个 Winforms C# 应用程序,它对 SQL Server 2012 数据库进行大量查询。这个数据库没有外键,在 winforms 应用程序中我有一个数据层,它使用System.Data.Common.DbConnection 对象管理与数据库的连接

这个 Winform 应用程序在打开时,它在主窗体上有一个计时器,用于保持连接打开,如下所示:

private void timerConexion_Tick(object sender, EventArgs e)
        {
            bool todoOK = false;
            if (this.Datos != null)
                if (this.Datos.Conexion != null)
                    if (this.Datos.Conexion.State == ConnectionState.Open)
                    {
                        todoOK = true;
                    }
            if (!todoOK)
            {
                this.timerConexion.Enabled = false;
                // The application must be close
                MessageBox.Show("¡Connection with the server is lost!\r\nThe application will be closed.", ConfigurationManager.AppSettings["TitleMessageBox"], MessageBoxButtons.OK, MessageBoxIcon.Stop);
                Application.Exit();
            }
        }

我的问题是我有 20 到 30 台 PC 运行这个 Winform 应用程序,并且有几次我有事务锁,我必须终止进程才能释放这些锁。 我正在考虑与数据库建立更好的连接,但我不知道最好的选择是什么……如果我使用实体框架会很痛苦……因为我的数据库上没有任何 FK。

我最好的选择是什么?

这是我的数据层:

[CLSCompliant(true)]
    public class Datos
    {

        private DbConnection conexion = null;

        public DbConnection Conexion
        {
            get { return conexion; }
        }

        private DbCommand comando = null;

        public DbCommand Comando
        {
            get { return comando; }
        }
        private DbTransaction transaccion = null;

        public DbTransaction Transaccion
        {
            get { return transaccion; }
        }
        private string cadenaConexion;

        public string CadenaConexion
        {
            get { return cadenaConexion; }
            set { cadenaConexion = value; }
        }

        private static DbProviderFactory factory = null;

        private ProveedorDatos proveedor;

        /// <summary>
        /// Crea una instancia del acceso a la base de datos.
        /// </summary>
        public Datos(ProveedorDatos proveedor)
        {
            this.proveedor = proveedor;
            Configurar();
        }

        /// <summary>
        /// Configura el acceso a la base de datos para su utilización.
        /// </summary>
        /// <exception cref="DatosException">Si existe un error al cargar la configuración.</exception>
        private void Configurar()
        {
            try
            {
                string proveedor = "";
                switch (this.proveedor)
                {
                    case ProveedorDatos.SqlServer:
                        proveedor = ConfigurationManager.AppSettings.Get("PROVEEDOR_ADONET");
                        this.cadenaConexion = ConfigurationManager.AppSettings.Get("CADENA_CONEXION");
                        break;
                    case ProveedorDatos.FoxDbf:
                        proveedor = ConfigurationManager.AppSettings.Get("PROVEEDOR_OLEDB");
                        this.cadenaConexion = ConfigurationManager.AppSettings.Get("CADENA_CONEXION_DBF");
                        break;
                }
                Datos.factory = DbProviderFactories.GetFactory(proveedor);
            }
            catch (ConfigurationException ex)
            {
                throw new DatosException("Error al cargar la configuración del acceso a datos.", ex);
            }
        }

        /// <summary>
        /// Permite desconectarse de la base de datos.
        /// </summary>
        public void Desconectar()
        {
            if (this.conexion.State.Equals(ConnectionState.Open))
            {
                this.conexion.Close();
            }
        }

        /// <summary>
        /// Se concecta con la base de datos.
        /// </summary>
        /// <exception cref="DatosException">Si existe un error al conectarse.</exception>
        public void Conectar()
        {
            if (this.conexion != null && !this.conexion.State.Equals(ConnectionState.Closed))
            {
                throw new DatosException("La conexión ya se encuentra abierta.");
            }
            try
            {
                if (this.conexion == null)
                {
                    this.conexion = factory.CreateConnection();
                    this.conexion.ConnectionString = cadenaConexion;
                }
                this.conexion.Open();
            }
            catch (SqlException ex)
            {
                throw new DatosException("Hubo un error en la instancia de SQL Server.", ex);
            }
            catch (DataException ex)
            {
                throw new DatosException("Error al conectarse a la base de datos.", ex);
            }
        }

        /// <summary>
        /// Aplica los roles de seguridad para el acceso a la base de datos
        /// </summary>
        public bool AplicarRoles(string roleApp, string roleAppPwd)
        {
            try
            {
                System.Data.SqlClient.SqlCommand commandoRoles = new System.Data.SqlClient.SqlCommand("EXEC sp_setapprole '" + roleApp + "', '" + roleAppPwd + "'", (System.Data.SqlClient.SqlConnection)conexion);
                int resultado = commandoRoles.ExecuteNonQuery();
                commandoRoles.Dispose();
                commandoRoles = null;
                if (resultado == -1)
                    return true;
                else
                    return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Crea un comando en base a una sentencia SQL.
        /// Ejemplo:
        /// <code>SELECT * FROM Tabla WHERE campo1=@campo1, campo2=@campo2</code>
        /// Guarda el comando para el seteo de parámetros y la posterior ejecución.
        /// </summary>
        /// <param name="sentenciaSQL">La sentencia SQL con el formato: SENTENCIA [param = @param,]</param>
        public void CrearComando(string sentenciaSQL)
        {
            if (this.comando != null)
            {
                this.comando.Dispose();
                this.comando = null;
            }
            this.comando = factory.CreateCommand();
            this.comando.Connection = this.conexion;
            this.comando.CommandType = CommandType.Text;
            this.comando.CommandText = sentenciaSQL;
            if (this.transaccion != null)
            {
                this.comando.Transaction = this.transaccion;
            }
        }

        public void CrearComandoStoredProcedure(string procedureName)
        {
            if (comando != null)
            {
                comando.Dispose();
                comando = null;
            }
            comando = factory.CreateCommand();
            comando.Connection = conexion;
            comando.CommandType = CommandType.StoredProcedure;
            comando.CommandText = procedureName;
        }

        public void AsignarParametro(string nombre, object valor, SqlDbType tipo)
        {
            var parameter = new SqlParameter(nombre, tipo)
            {
                Value = valor ?? DBNull.Value
            };
            comando.Parameters.Add(parameter);
        }

        /// <summary>
        /// Setea un parámetro como nulo del comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro cuyo valor será nulo.</param>
        public void AsignarParametroNulo(string nombre)
        {
            AsignarParametro(nombre, "", "NULL");
        }

        /// <summary>
        /// Asigna un parámetro de tipo cadena al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroCadena(string nombre, string valor)
        {
            AsignarParametro(nombre, "'", valor.ToString());
        }

        /// <summary>
        /// Asigna un parámetro de tipo entero al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroEntero(string nombre, int valor)
        {
            AsignarParametro(nombre, "", valor.ToString());
        }

        /// <summary>
        /// Asigna un parámetro de tipo entero al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroEntero(string nombre, long valor)
        {
            AsignarParametro(nombre, "", valor.ToString());
        }

        /// <summary>
        /// Asigna un parámetro de tipo numerico al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroNumerico(string nombre, double valor)
        {
            AsignarParametro(nombre, "", valor.ToString().Replace(',', '.'));
        }

        /// <summary>
        /// Asigna un parámetro de tipo bit al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroBit(string nombre, bool valor)
        {
            if (valor)
                AsignarParametro(nombre, "", "1");
            else
                AsignarParametro(nombre, "", "0");
        }

        ///// <summary>
        ///// Asigna un parámetro de tipo numeric al comando creado.
        ///// </summary>
        ///// <param name="nombre">El nombre del parámetro.</param>
        ///// <param name="valor">El valor del parámetro.</param>
        //public void AsignarParametroNumerico(string nombre, double valor)
        //{
        //    AsignarParametro(nombre, "", valor.ToString());
        //}

        /// <summary>
        /// Asigna un parámetro al comando creado.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="separador">El separador que será agregado al valor del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        private void AsignarParametro(string nombre, string separador, string valor)
        {
            int indice = this.comando.CommandText.IndexOf(nombre);
            string prefijo = this.comando.CommandText.Substring(0, indice);
            string sufijo = this.comando.CommandText.Substring(indice + nombre.Length);
            this.comando.CommandText = prefijo + separador + valor + separador + sufijo;
        }

        /// <summary>
        /// Asigna un parámetro de tipo fecha al comando.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroFecha(string nombre, DateTime valor)
        {
            var parameter = new SqlParameter(nombre, SqlDbType.DateTime)
            {
                Value = valor
            };
            comando.Parameters.Add(parameter);
            //AsignarParametro(nombre, "'", valor.ToString("yyyyMMdd HH:mm:ss"));
        }

        /// <summary>
        /// Asigna un parámetro de tipo fecha al comando.
        /// </summary>
        /// <param name="nombre">El nombre del parámetro.</param>
        /// <param name="valor">El valor del parámetro.</param>
        public void AsignarParametroFechaCorta(string nombre, DateTime valor)
        {
            var parameter = new SqlParameter(nombre, SqlDbType.DateTime)
            {
                Value = valor
            };
            comando.Parameters.Add(parameter);
            //AsignarParametro(nombre, "'", valor.ToString("yyyyMMdd"));
        }

        /// <summary>
        /// Ejecuta el comando creado y retorna el resultado de la consulta.
        /// </summary>
        /// <returns>El resultado de la consulta.</returns>
        /// <exception cref="DatosException">Si ocurre un error al ejecutar el comando.</exception>
        public DbDataReader EjecutarConsulta()
        {
            this.comando.CommandTimeout = 120;
            return this.comando.ExecuteReader();
        }

        /// <summary>
        /// Ejecuta el comando creado y retorna el resultado de la consulta en un DbDataAdapter.
        /// </summary>
        /// <returns>El resultado de la consulta.</returns>
        /// <exception cref="DatosException">Si ocurre un error al ejecutar el comando.</exception>
        public DbDataAdapter EjecutarConsultaDS()
        {
            DbDataAdapter adaptador = factory.CreateDataAdapter();
            adaptador.SelectCommand = comando;
            return adaptador;
        }

        /// <summary>
        /// Ejecuta el comando creado y retorna un escalar.
        /// </summary>
        /// <returns>El escalar que es el resultado del comando.</returns>
        /// <exception cref="DatosException">Si ocurre un error al ejecutar el comando.</exception>
        public int EjecutarEscalar()
        {
            int escalar = 0;
            try
            {
                object exit = this.comando.ExecuteScalar();
                if (exit != null)
                {
                    int.TryParse(exit.ToString(), out escalar);
                }
            }
            catch (InvalidCastException ex)
            {
                throw new DatosException("Error al ejecutar un escalar.", ex);
            }
            return escalar;

        }


        public T EjecutarEscalar<T>()
        {
            try
            {
                object exit = this.comando.ExecuteScalar();
                Type t = typeof(T);
                t = Nullable.GetUnderlyingType(t) ?? t;

                return (exit == null || DBNull.Value.Equals(exit)) ?
                   default(T) : (T)Convert.ChangeType(exit, t);
            }
            catch (InvalidCastException ex)
            {
                throw new DatosException("Error al ejecutar un escalar.", ex);
            }
        }

        public T EjecutarValorStoredProcedure<T>(string returnName, SqlDbType returnType)
        {
            var parameter = new SqlParameter(returnName, returnType)
            {
                Direction = ParameterDirection.ReturnValue
            };
            comando.Parameters.Add(parameter);
            comando.ExecuteNonQuery();
            object value = comando.Parameters[returnName].Value;
            return (T) value;
        }

        /// <summary>
        /// Ejecuta el comando creado.
        /// </summary>
        public void EjecutarComando()
        {
            this.comando.ExecuteNonQuery();
        }

        /// <summary>
        /// Ejecuta el comando creado.
        /// </summary>
        public int EjecutarComandoInt()
        {
            return this.comando.ExecuteNonQuery();
        }


        /// <summary>
        /// Comienza una transacción en base a la conexion abierta.
        /// Todo lo que se ejecute luego de esta ionvocación estará 
        /// dentro de una tranasacción.
        /// </summary>
        public void ComenzarTransaccion()
        {
            if (this.transaccion == null)
            {
                this.transaccion = this.conexion.BeginTransaction(IsolationLevel.Serializable);
            }
        }

        /// <summary>
        /// Cancela la ejecución de una transacción.
        /// Todo lo ejecutado entre ésta invocación y su 
        /// correspondiente <c>ComenzarTransaccion</c> será perdido.
        /// </summary>
        public void CancelarTransaccion()
        {
            if (this.transaccion != null)
            {
                this.transaccion.Rollback();
            }
            this.transaccion.Dispose();
            this.transaccion = null;
        }

        /// <summary>
        /// Confirma todo los comandos ejecutados entre el <c>ComanzarTransaccion</c>
        /// y ésta invocación.
        /// </summary>
        public void ConfirmarTransaccion()
        {
            if (this.transaccion != null)
            {
                this.transaccion.Commit();
            }
            this.transaccion.Dispose();
            this.transaccion = null;
        }

    }

编辑:我忘了说Connection是在主窗体的Program.csMain方法中创建的,如下:

Datos datos = new Datos(ProveedorDatos.SqlServer);
datos.Conectar();
Application.Run(new FrmApp(datos));
datos.Desconectar();

【问题讨论】:

  • 连接保持打开状态是不是有特定的原因,为什么不越晚打开连接越早关闭呢?
  • 这可能是锁的原因?问题是这个 Winform 应用程序是这样送到我这里的。我不知道那个持续连接的应用程序的主要原因。
  • 保持连接打开并不少见,但很少需要它,例如插入一批数据或执行多个相关语句,除了没有理由保持连接打开。如果您可以修改应用程序,请执行此操作。
  • 整个应用程序都在使用数据库中的数据。所以也许这就是始终打开连接的原因。
  • 当我指的是整个应用程序时,我的意思是每个表单都使用数据库的某些东西

标签: c# sql .net sql-server winforms


【解决方案1】:

我认为问题在于每次函数调用 factory.CreateConnection() 时您都在创建一个新连接,因此您正在创建多个连接。我没有看到您是否正在处理它(connection.Dispose())。

尝试两件事:

  • 在创建新连接时使用“using”关键字,因此您不必担心将其丢弃。

  • 实现一个单例或多例进行分离:

    单例是这样的:

    public class Singleton {
      private static Singleton theInstance;
      private Singleton(){
        theInstance=null;
      }
      public static Singleton getInstance(){
       if ( theInstance==null )
         theInstance= new Singleton();
         return theInstance;
       }
    }
    

    您有一个连接类的实例,当该实例为空时,您创建一个新实例,但当实例已经存在时,您返回该实例。这保证了只会创建一个连接。这有很多实现,请尝试使用固定数量的连接来实现,而不仅仅是一个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 2021-07-08
    • 1970-01-01
    相关资源
    最近更新 更多