首页 > 解决方案 > SQLite错误提供给命令的参数不足

问题描述

当试图更改下表1中的活动列时。

我得到错误

错误提供给命令的参数不足

而且我一生都无法弄清楚代码有什么问题。请帮忙。

private void dataGridView1_SelectionChanged_1(object sender, EventArgs e)
{
        SQLiteConnection sqlConnection = new SQLiteConnection();
        sqlConnection.ConnectionString = "datasource = SubjectTable.db";

        if (dataGridView1.SelectedRows.Count > 0)
        {
            ID = dataGridView1.SelectedRows[0].Cells[1].Value.ToString();

            //Define SELECT Statement
            string commandText = "SELECT * FROM SubjectTable WHERE ID=" + ID;

            //Create a datatable to save data in memory 
            var datatable = new DataTable();
            SQLiteDataAdapter myDataAdapter = new SQLiteDataAdapter(commandText, sqlConnection);

            sqlConnection.Open();

            //Fill data from database into datatable
            myDataAdapter.Fill(datatable);

            //Fill data from datatable into form controls
            CMBactive.Text = datatable.Rows[0]["Active"].ToString();
            TBsubjectBUD.Text = datatable.Rows[0]["Budget"].ToString();

            sqlConnection.Close();
    }
}

标签: c#winformssqlite

解决方案


你会遇到这些问题,因为你试图在一个程序中做太多的事情。你应该分开你的顾虑

将从数据库中获取数据与显示该数据分开;另外,您这样做的事件选择已更改。

这样做的好处是您可以更轻松地重用代码:如果您想因为按下按钮而做同样的事情,您可以重用代码。之后,如果您想添加一个执行相同操作的菜单项,则它是一个单行代码。

如果您有一个单独的方法来查询数据库,或者一个单独的方法来填充您的控件 CmbActive 和 TbSubjectBud,那么测试您的代码会更容易。

更改代码更容易,例如,如果您不再使用 SQLite,但实体框架来获取您的数据,显示和按钮处理不会注意到这一点。只需要更改获取数据的过程。

这使得单元测试更容易:您可以使用 Dictionary 来模拟数据库,而不是真正的数据库。

最后:在使用Winforms的时候,不要直接摆弄Cells,使用DataGridView的DataSource来填充和读取数据。再次:将数据与其显示方式分开。

首先你的实际问题:查询数据

因此,您有一个 Id,并且您想从具有此 Id 的数据库中的所有主题中获取列Active的值。Budget不要获取您不会使用的属性!

数据库处理

首先,我们需要一个 Subject 类来放置您从 table 获取的数据SubjectTable。如果你把这个表的所有列都放在里面,你可以将该类重用于其他查询。然而。您不必填写所有字段。这取决于您调用此方法的频率是填充所有属性还是仅填充一些属性是明智的。

有些人不喜欢这样。考虑始终获取所有列(效率低下),或为不同的查询创建类(大量工作)。

class Subject
{
    public int Id {set; set;}
    public string Name {get; set;}
    public DateTime StartDate {get; set;}

    public string Active {get; set;}
    public Decimal Budget {get; set;}
}

创建一个方法以从具有 Id 的 tableSubjects 中获取 Active 和 Budget,如果没有具有此 Id 的主题,则为 null。

将所有数据库查询放在一个单独的类中。例如类Repository。您隐藏它在数据库中,如果将来您想将其保存为 CSV 文件或 JSON 格式,没有人会注意到(如果您想在单元测试中使用它,那就太好了!)

private Subject FetchBudgetOrDefault(int id)
{
    const string sqlText = @"SELECT Active, Budget FROM SubjectTable WHERE ID = @Id";
    using (var dbConnection = new SQLiteConnection(this.dbConnectionString))
    {
         using (var dbCommand = dbConnection.CreateCommand()
         {
             dbCommand.Commandtext = sqlText;
             dbCommand.Parameters.AddWithValue("@Id", id);
             using (var dbReader = dbCommand.ExecuteReader())
             {
                 if (dbReader.Read())
                 {
                     // There is a Subject with this id:
                     return new Subject()
                     {
                         Id = id,
                         Active = dbReader.GetString(0),
                         Budget = (decimal)dbReader.GetInt64(1) / 100.0D,
                     };
                 }
                 else
                 {
                      // no subject with this Id
                      return null;
                 }
             }
         }
     }
 }

我假设小数点Budgetlong * 100故意保存的,以向您展示通过分离您的关注点,无需更改所有用户就可以很容易地更改数据库布局:如果您想在 SQLite 中将此小数点保存为 REAL,那么查询是您必须更改数据的唯一地方。

顺便说一句:这个方法也解决了你的问题:ID不能是空字符串!

如果您不会每秒执行 1000 次此查询,请考虑获取主题的所有列。这效率有点低,但更容易测试、重用和维护。

在表单中显示获取的数据

目前,您在 ComboBox 和 TextBox 中显示数据。如果您将关注点分开,那么您将只在一个地方执行此操作。如果你想在表格中显示数据,或者用它做其他事情,你只需要改变一个地方:

public void Display(Subject subject)
{
    this.comboBoxActive.Text = subject.Active;
    this.textBoxBudget.Text = subject.Budget.ToString(...);
}

奖励积分:如果您想更改显示预算的格式,您只需在此处执行此操作。

读取和写入 DataGridView

直接读取和写入 DataGridView 的单元格很少是一个好主意。这是很多工作的方式。您必须自己进行所有类型检查。大量工作来测试和实现显示数据中的小变化。

使用数据源更容易。

在 DataGridView 的 DataSource 中,您放置了一系列相似的项目。如果你只想显示一次,一个ICollection<TSource>就足够了(数组,列表)。如果要自动更新更改,请使用BindingList<TSource>

在 DataGridView 添加列。用户属性DataGridViewColumn.DataPropertyName,用于指示应在该列中显示哪个属性。

通常使用 Visual Studio Designer 添加列就足够了。

如果您的 datagridview 显示主题,代码将如下所示:

DataGridView dgv1 = new DataGridView();
DataGridViewColumn columnId = new DataGridViewColumn
{
    DataPropertyName = nameof(Subject.Id),
    ...
};
DataGridView columnName = new DataGridViewColumn
{
    DataPropertyName = nameof(Subject.Name),
    ...
};
... // other columns
dgv.Columns.Add(columnId);
dgv.Columns.Add(columnName);
...

在您的表单类中:

private BindingList<Subject> DisplayedSubjects {get; set;} = new BindingList<Subject>();

// Constructor:
public MyForm()
{
    InitializeComponent();
    this.dgv1.DataSource = this.DisplayedSubjects();
}
void FillDataGridView()
{

    using (var repository = new Repository())
    {
        IEnumerable<Subject> fetchedSubjects = repository.FetchAllSubjects();
        this.DisplayedSubjects = new BindingList<Subject>(fetchedSubjects.ToList();

    }
}

这就是显示所有获取的主题所需的全部内容。如果操作员更改任何单元格值,this.DislayedSubjects则会自动更新相应的值。这适用于两种方式:如果您更改 中的任何值this.DisplayedSubjects,DataGridView 中显示的值将自动更新。

无需直接读取单元格。如果您允许列重新排序,或者如果您实现行排序,那么一切仍然可以通过两种方式进行。因为您将获取的数据与显示的数据分开,您可以更改显示而无需更改获取的数据。

把它们放在一起

当您收到从要更新活动和预算的数据网格视图更改选择的事件时。让我们从 Selected 项目开始:

void OnSelectionChanged(object sender, EventHandler e)
{
    // Get the selected Subject
    var selectedSubject = this.SelectedSubject;
    this.Display(selectedSubject); // described above
}

Subject SelectedSubject => this.Dgv.SelectedRows.Cast<DataGridViewRow>()
                               .Select(row => (Subject)row.DataBoundItem)
                               .FirstOrDefault();

因为您分离了关注点,所以每个方法都易于理解、易于测试、易于重用并稍作更改:如果您想在 Button Press 或菜单项之后进行更新,代码将是单行的。如果您想显示除 Active / Budget 之外的其他项目:小的更改;如果您想按名称而不是 Id 获取:只需要进行有限的更改。


推荐阅读