尝试更改下表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中获取列Active
和Budget
的值。不要获取您不会使用的属性!
数据库处理
首先,我们需要一个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提取:只需要有限的更改。