Short Version
How do i call:
dataset1.FieldByName(fieldName).AsString := 'Something';
and have it work?
Long version
I have a DataSet:
var
ds: TDataSet;
ds := GetSomeSortOfDataSetFromSomewhere();
This dataset will be exported (e.g. to Excel, cSV, TSV, Markdown, HTML, XML):
ExportDataSet(ds);
and the export will contain all columns and all rows:
| Username | Fullname |
|---|---|
| ian | IAN BOYD |
| MartynA | MARTIN |
| ngal | NASREDDINE GALFOUT |
| uewr | UWE RAABE |
Now i want to modify the Fullname field for each row in-memory before doing something else with it (i.e. it’s never going back into a database, i don’t know where it came from , it might not have come from a database):
while not ds.EOF do
begin
ds.FieldByName('Fullname').AsString := FormatNamePrettyLike(ds.FieldByName('Fullname').AsString;
ds.Next;
end;
Trying to modify a field gives the exception:
Dataset not in edit or insert mode
The solution is to clone the dataset into an in-memory TClientDataset:
///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
tempProvider: TDataSetProvider;
data: OleVariant;
ds: TClientDataSet;
begin
tempProvider := TDataSetProvider.Create(nil);
try
tempProvider.DataSet := dsSource;
data := tempProvider.Data;
finally
tempProvider.Free;
end;
ds := TClientDataSet.Create(nil);
ds.Data := data;
Result := ds;
end;
Which gives larger code:
var
ds: TDataset;
dsEditable: TDataSet;
ds := GetDataSomeOfSomeSortFromSomewhere();
//Clone to dataset to an in-memory dataset so we can modify it.
dsEditable := CloneDataSet(ds);
ds.Free;
ds := edEditable;
while not ds.EOF do
begin
ds.FieldByName('Fullname').AsString := FormatNamePrettyLike(ds.FieldByName('Fullname').AsString;
ds.Next;
end;
But this gives the error:
Dataset not in edit or insert mode
The solution is to put the dataset in edit mode:
//The in-memory ClientDataSet won't be editable until you mark it editable.
ds.Edit;
///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
tempProvider: TDataSetProvider;
data: OleVariant;
ds: TClientDataSet;
begin
tempProvider := TDataSetProvider.Create(nil);
try
tempProvider.DataSet := dsSource;
data := tempProvider.Data;
finally
tempProvider.Free;
end;
ds := TClientDataSet.Create(nil);
ds.Data := data;
//The in-memory ClientDataSet won't be editable until you mark it editable.
ds.Edit;
Result := ds;
end;
Repeating the excerise now gives the error:
Field
Fullnamecannot be modified.
The solution is to set Field.ReadOnly to false:
//Even after marking the in-memory data-set as editable, you still can't edit it
//until you mark all fields as editable.
for i := 0 to ds.FieldCount-1 do
ds.Fields[i].ReadOnly := False;
///<summary>Clones a dataset into a TClientDataSet; which is an editable in-memory DataSet.</summary>
function CloneDataSet(dsSource: TDataSet): TDataSet; //TDataSet > TCustomClientDataSet > TClientDataSet
var
tempProvider: TDataSetProvider;
data: OleVariant;
ds: TClientDataSet;
begin
tempProvider := TDataSetProvider.Create(nil);
try
tempProvider.DataSet := dsSource;
data := tempProvider.Data;
finally
tempProvider.Free;
end;
ds := TClientDataSet.Create(nil);
ds.Data := data;
//The in-memory ClientDataSet won't be editable until you mark it editable.
ds.Edit;
//Even after marking the in-memory data-set as editable, you still can't edit it
//until you mark all fields as editable.
for i := 0 to ds.FieldCount-1 do
ds.Fields[i].ReadOnly := False;
Result := ds;
end;
Repeating the exercise gives the error:
Trying to modify read-only field.
So i give up. How do i edit a DataSet field?
The cloned in-memory TCustomClientDataSet contents are all there; i just want to edit them on the client for display purposes.
Bonus Chatter
Obviously i can’t be adding new columns to the data set:
| Username | Fullname | PrettyFullname |
|---|---|---|
| ian | IAN BOYD | Ian Boyd |
| MartynA | MARTIN | Martin |
| ngal | NASREDDINE GALFOUT | Nasreddine Galfout |
| uewr | UWE RAABE | Uwe Raabe |
Obviously i can’t attach an event handler to the data set:
- as that data handler would be invalid when the data set is passed onto the next person in a chain (e.g. a thread), and the original form is freed
- and it’s also not what i’m asking; which is about modifying the contents of a data set
- the updating of values hits other systems (e.g. databases, web-services, etc). I want the changes done once, and then in the data set
Последнее обновление: 14.02.2021
При работе с данными в DataSet мы можем производить с ними различными операции: удалять, изменять, добавлять новые записи. Но все производимые с данными изменения
автоматически не будут сохраняться в БД. Для этого нам еще надо вызвать метод Update объекта SqlDataAdapter,
который заполнял объект DataSet.
Для модификации данных в БД в соответствии с изменениями в DataSet SqlDataAdapter использует команды
InsertCommand, UpdateCommand и DeleteCommand. Мы можем сами определить для этих команд
sql-выражения, либо мы можем воспользоваться классом SqlCommandBuilder, который позволяет автоматически сгенерировать нужные выражения.
Например, добавим в DataSet строку, изменим уже имеющиеся данные и используем SqlCommandBuilder для сохранения объектов в базу данных:
using System;
using System.Data;
using Microsoft.Data.SqlClient;
namespace HelloApp
{
class Program
{
static void Main(string[] args)
{
string connectionString = "Server=(localdb)\mssqllocaldb;Database=adonetdb;Trusted_Connection=True;";
string sql = "SELECT * FROM Users";
using (SqlConnection connection = new SqlConnection(connectionString))
{
// Создаем объект DataAdapter
SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
// Создаем объект Dataset
DataSet ds = new DataSet();
// Заполняем Dataset
adapter.Fill(ds);
DataTable dt = ds.Tables[0];
// добавим новую строку
DataRow newRow = dt.NewRow();
newRow["Name"] = "Rick";
newRow["Age"] = 24;
dt.Rows.Add(newRow);
// Изменим значение в столбце Age для первой строки
dt.Rows[0]["Age"] = 17;
// создаем объект SqlCommandBuilder
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
adapter.Update(ds);
// альтернативный способ - обновление только одной таблицы
//adapter.Update(dt);
// заново получаем данные из бд
// очищаем полностью DataSet
ds.Clear();
// перезагружаем данные
adapter.Fill(ds);
// Отображаем данные
// перебор всех таблиц
foreach (DataColumn column in dt.Columns)
Console.Write($"{column.ColumnName}t");
Console.WriteLine();
// перебор всех строк таблицы
foreach (DataRow row in dt.Rows)
{
// получаем все ячейки строки
var cells = row.ItemArray;
foreach (object cell in cells)
Console.Write($"{cell}t");
Console.WriteLine();
}
}
Console.Read();
}
}
}
Консольный вывод в моем случае:
Id Age Name 2 17 Alice 3 28 Bob 19 36 Tom 22 34 Tim 23 31 Kate 24 26 Rick
Здесь после загрузки данных создается новая строка, которая затем добавляется в DataTable. При вызове у адаптера метода Update()
происходит анализ изменений, которые произошли. И после этого выполняется соответствующая команда. В данном случае так как идет добавление
новой строки, то будет выполняться команда InsertCommand. Однако в данном коде мы нигде явным образом не задаем эту команду, за нас все автоматически
делает SqlCommandBuilder. Для применения этого класса достаточно вызвать его конструктор, в который передается нужный адаптер:
SqlCommandBuilder commandBuilder = new SqlCommandBuilder(adapter);
Причем больше нигде в коде вы этот объект не вызываем.
При необходимости мы можем получить sql-выражения используемых команд:
Console.WriteLine(commandBuilder.GetUpdateCommand().CommandText); Console.WriteLine(commandBuilder.GetInsertCommand().CommandText); Console.WriteLine(commandBuilder.GetDeleteCommand().CommandText);
В моем случае команда обновления будет выглядеть так:
UPDATE [Users] SET [Name]=@p1, [Age]=@p2 WHERE (([Id]=@p3) AND ([Name]=@p4) AND ([Age]=@p5))
Команда вставки:
INSERT INTO [Users] ([Name],[Age]) VALUES (@p1, @p2)
Команда удаления:
DELETE FROM [Users] WHERE (([Id]=@p1) AND ([Name]=@p2) AND ([Age]=@p3))
I have a Dataset with a TableAdapter and when i say something like this
this.medicineDatasetTableAdapter.Fill(this.statisticsDataset.MedicineDataset);
It brings me all the results from a database and i show them using a pivot from DevExpress control.
I am using sqlite as database and i store in a filed datetime eg ‘1/4/2012’ as string and when is the time to be show from the pivot with a grouping as DateMonth or whatever.. nothing is show up. I think that the pivot doesnt understand the format to group the dates..
Now i try to get the DataTable from the DataSet with the GetData command and change the datetime to a correct date and fill it again with the correct dates results.
One way i tryied is like
StatisticsDataset.MedicineDatasetDataTable datatable = this.medicineDatasetTableAdapter.GetData();
DataTable a = this.medicineDatasetTableAdapter.GetData();
foreach (DataRow row in a.Rows)
{
DateTime tempDate = DateTime.ParseExact(row.ItemArray[4].ToString(), Resources.ShortDateFormat, new CultureInfo(Resources.CurrentCulture));
row[4] = tempDate;
}
this.statisticsDataset.MedicineDataset = a;
//this.medicineDatasetTableAdapter.Fill(this.statisticsDataset.MedicineDataset);
this.medicineDatasetTableAdapter.Fill((MedExpress.Dataset.StatisticsDataset.MedicineDatasetDataTable)a);
But it doesnt works. Any suggestions please??
asked Mar 1, 2012 at 11:08
Certainly seems to be a type issue.
Looking at the code you’re taking a data from a ‘Text’ cell, converting it into a DateTime and then copying the value back into the same ‘Text’ cell (which would cast the value back into a string!). I would suggest either…
-
Create a new strongly typed DateTime cell for your MedicineDatasetDataTable and copy the parsed value into that cell.
-
Why bother storing DateTime values as string, virtually all database products (sorry I’ve never used SQLLite) support this datatype and in a strongly typed scenario, there’s no need to convert values.
answered Mar 1, 2012 at 12:55
SeanCocteauSeanCocteau
1,83819 silver badges25 bronze badges
1
I have a Dataset with a TableAdapter and when i say something like this
this.medicineDatasetTableAdapter.Fill(this.statisticsDataset.MedicineDataset);
It brings me all the results from a database and i show them using a pivot from DevExpress control.
I am using sqlite as database and i store in a filed datetime eg ‘1/4/2012’ as string and when is the time to be show from the pivot with a grouping as DateMonth or whatever.. nothing is show up. I think that the pivot doesnt understand the format to group the dates..
Now i try to get the DataTable from the DataSet with the GetData command and change the datetime to a correct date and fill it again with the correct dates results.
One way i tryied is like
StatisticsDataset.MedicineDatasetDataTable datatable = this.medicineDatasetTableAdapter.GetData();
DataTable a = this.medicineDatasetTableAdapter.GetData();
foreach (DataRow row in a.Rows)
{
DateTime tempDate = DateTime.ParseExact(row.ItemArray[4].ToString(), Resources.ShortDateFormat, new CultureInfo(Resources.CurrentCulture));
row[4] = tempDate;
}
this.statisticsDataset.MedicineDataset = a;
//this.medicineDatasetTableAdapter.Fill(this.statisticsDataset.MedicineDataset);
this.medicineDatasetTableAdapter.Fill((MedExpress.Dataset.StatisticsDataset.MedicineDatasetDataTable)a);
But it doesnt works. Any suggestions please??
asked Mar 1, 2012 at 11:08
Certainly seems to be a type issue.
Looking at the code you’re taking a data from a ‘Text’ cell, converting it into a DateTime and then copying the value back into the same ‘Text’ cell (which would cast the value back into a string!). I would suggest either…
-
Create a new strongly typed DateTime cell for your MedicineDatasetDataTable and copy the parsed value into that cell.
-
Why bother storing DateTime values as string, virtually all database products (sorry I’ve never used SQLLite) support this datatype and in a strongly typed scenario, there’s no need to convert values.
answered Mar 1, 2012 at 12:55
SeanCocteauSeanCocteau
1,83819 silver badges25 bronze badges
1
