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();
}
}

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

将从数据库中提取数据与显示该数据分开;分开也说明你做这件事的选择变了。

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

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

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

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

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

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

因此,您有一个Id,并且希望从具有该Id的数据库中的所有Subject中获取列ActiveBudget的值。不要获取您不会使用的属性!

数据库处理

首先,我们需要一个Subject类来放置从表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为的表主题中获取活动和预算,如果没有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;
}
}
}
}
}

我假设小数点Budget是故意保存为long * 100的,以向您表明,通过分离您的关注点,在不必更改所有用户的情况下更改数据库布局是相当容易的:如果您想将SQLite中的小数点保存为REAL,那么查询是唯一需要更改数据的地方。

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

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

在表单中显示提取的数据

当前,您在组合框和文本框中显示数据。如果你把你的顾虑分开,那么只有一个地方可以让你这样做。如果你想在表格中显示数据,或者用它做其他事情,你只需要改变一个地方:

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

加分:如果你想更改显示预算的格式,你只需要在这里这样做。

读写DataGridView

直接读写DataGridView的单元格很少是个好主意。这是许多工作的方式。你必须自己做所有的类型检查。测试和实现显示数据中的小更改需要做大量工作。

使用DataSource要容易得多。

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

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

通常,使用visualstudio设计器来添加列就足够了。

如果您的数据网格视图显示主题,代码将如下所示:

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中显示的值将自动更新。

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

把它们放在一起

当您收到数据网格视图中的选择发生更改的事件时,您需要更新"活动"one_answers"预算"。让我们从所选项目中进行:

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();

因为您分离了关注点,所以每个方法都易于理解、易于测试、易于重用,并且可以稍微更改:如果您想在按下按钮或菜单项后更新,代码将是一行代码。如果您想显示除活动/预算之外的其他项目:小的更改;如果您想按名称而不是Id提取:只需要有限的更改。