Scenario:
Excel file is read and displayed in datagrid.
Values in sql server must be updated if excel values are different.
Table in Sql server don't have primary key
After all these steps, when I am about to update the table, it throws the error saying "Update requires a valid UpdateCommand when passed DataRow collection with modified rows."
There is no primary key. So I need to use update command. BUt how and what would be in update command? importdata is dictionary where data from excel are stored. PLz help!!! What should I do now? I have No idea....
foreach (DataColumn column in ds.Tables[0].Columns)
{
string fieldName = column.ColumnName;
string fieldNameValueE = string.Empty;
if (importdata.ContainsKey(fieldName))
{
fieldNameValueE = importdata[fieldName];
foreach (DataRow dr in ds.Tables[0].Rows)
{
string fieldNameValueD = dr[fieldName].ToString();
if (fieldNameValueD != fieldNameValueE)
{
dr[fieldName] = fieldNameValueE;
}
}
}
}
da.Update(ds);
connection.Close();
So, let's say we were dealing with a table that had a primary key:
CREATE TABLE TableA
{
FieldA INT PRIMARY KEY IDENTITY(1, 1),
FieldB VARCHAR(256) NULL,
FieldC VARCHAR(256) NOT NULL,
}
If you were to use the SqlCommandBuilder (which you cannot because you don't have a primary key), it would build a statement a bit like this:
UPDATE TableA
SET FieldB = #p1,
FieldC = #p2
WHERE (FieldA = #p3 AND
((FieldB IS NULL AND #p4 IS NULL) OR (FieldB = #p5)) AND
FieldC = #p6)
So, you're going to need to build an UPDATE statement that's very similar to do it the way they do. But one thing you need to remember is it's not just the statement, you also have to add all of the parameters to the command that you build - and that's going to become pretty cumbersome.
So, I have two recommendations for you:
Make a primary key on the table.
Issue an ExecuteNonQuery in every iteration of the loop.
The second recommendation would look like this:
foreach (DataColumn column in ds.Tables[0].Columns)
{
string fieldName = column.ColumnName;
string fieldNameValueE = string.Empty;
if (importdata.ContainsKey(fieldName))
{
fieldNameValueE = importdata[fieldName];
foreach (DataRow dr in ds.Tables[0].Rows)
{
string fieldNameValueD = dr[fieldName].ToString();
if (fieldNameValueD != fieldNameValueE)
{
dr[fieldName] = fieldNameValueE;
}
var cmd = new SqlCommand(string.Format(
"UPDATE importdata SET {0} = {1} WHERE fielda = #fielda AND fieldb = #fieldb ...",
fieldName, fieldNameValueE), connectionObject);
cmd.Parameters.AddWithValue(#fielda, dr["fielda", DataRowVersion.Original]);
cmd.Parameters.AddWithValue(#fieldb, dr["fieldb", DataRowVersion.Original]);
cmd.ExecuteNonQuery();
}
}
}
Use SqlCommandBuilder.
After setting up your DataAdapter, initialize a command builder.
Then use the SqlCommandBuilder update feature.
SqlCommandBuilder cb = new SqlCommandBuilder(da);
//Do your other work updating the data
cb.DataAdapter.Update(ds);
That should do the trick!
Edit:
As BigM pointed out, your table needs to have a primary key for the SqlCommandBuilder to work. If however, you can't modify the actual SQL table and mark one of the fields as a Primary Key and you also know that one of the fields is unique, you can add a Primary Key to your DataSet table like this:
DataColumn[] pk1 = new DataColumn[1];
pk1[0] = ds.Tables["TableName"].Columns[0];
ds.Tables["TableName"].PrimaryKey = pk1;
This gives your "in memory" table a primary key so that the SqlCommandBuilder can do its work.
Warning:
You must be sure that the values in the column you mark as primary key are actually unique.
Related
I'm trying to use SqlBulkCopy to insert a number of rows into a table where the Id column is set with a sequence. The sequence and table look something like:
CREATE SEQUENCE [dbo].[MyTableId]
AS [int]
START WITH 1
INCREMENT BY 1
MINVALUE -2147483648
MAXVALUE 2147483647
CACHE 10
GO
CREATE TABLE [dbo].[MyTable](
[Id] [int] NOT NULL,
[SomeColumn] [int] NOT NULL,
[AnotherColumn] [nvarchar](100) NOT NULL
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
GO
ALTER TABLE [dbo].[MyTable] ADD
CONSTRAINT [DF_MyTable_Id]
DEFAULT (NEXT VALUE FOR [MyTableId]) FOR [Id]
GO
The Code to import the rows looks something like:
var table = new DataTable();
using (var adapter = new SqlDataAdapter($"SELECT TOP 0 * FROM dbo.MyTable", conn))
{
adapter.Fill(table);
}
foreach (Data d in data)
{
var row = table.NewRow();
row["SomeColumn"] = d.someColumnValue;
row["AnotherColumn"] = d.anotherColumnValue;
table.Rows.Add(row);
}
using (var bulk = new SqlBulkCopy(conn))
{
bulk.DestinationTableName = "dbo.MyTable";
bulk.WriteToServer(table);
}
This fails when writing the data to the server because
System.InvalidOperationException: 'Column 'Id' does not allow DBNull.Value.'
I have tried removing the Id column from the table definition, but this just puts the column ordinals off by one. I've tried setting the Id row to:
table.Columns["Id"].AutoIncrement = true;
but this ignores the sequence, and on repeated runs of the import, restarts the auto-increment value from 0.
How would I instruct the server to generate a new Id value using its sequence? Or is it possible to separately generate a number of values for the sequence prior to creating the new rows in the table?
What about asking SQL Server for the current sequence value, through a SqlCommand.ExecuteScalar(). Use this SQL statement as the input for the command:
SELECT current_value
FROM sys.sequences
WHERE OBJECT_ID = OBJECT_ID(N'dbo.MyTableId');
Then set column property AutoIncrementSeed to the previous value plus one:
// ... code to get current sequence value
string sqlText = <above sql goes here>;
SqlCommand getSeqValue = new(sqlText, your_connection);
long currentSequenceValue = (long)getSeqValue.ExecuteScalar();
// Construct DataTable structure
// maybe replacing adapter.Fill(table);
// with
//adapter.FillSchema(table,SchemaType.Source);
// tell table to start ID on current sequence value + 1 (or AutoIncrementStep)
table.Columns["Id"].AutoIncrement = true;
table.Columns["Id"].AutoIncrementSeed = currentSequenceValue + 1;
// prepare things and bulk insert
Just an idea, haven't tested. :/
I would like to ask for your help. The problem is that I am trying to relate two tables so I need to create primary keys on the tables each "id" columns but I am getting an error on this line
ds.Relations.Add(dr);
Error:
This constraint cannot be enabled as not all values have corresponding parent values.
(original error in Spanish = No se puede habilitar esta restricción ya que todos los valores no tienen los valores primarios correspondientes.)
What am I doing wrong?
OdbcDataAdapter sdata = new OdbcDataAdapter("SELECT * FROM componentes WHERE nombre_componente ILIKE '%" + buscar + "%'", conn);
OdbcDataAdapter sdata2 = new OdbcDataAdapter("SELECT * FROM proveedores WHERE nombre_empresa ILIKE '%" + buscar + "%'", conn);
DataSet ds = new DataSet();
DataTable dtbl = new DataTable("dtbl");
DataTable dtbl2 = new DataTable("dtbl2");
sdata.Fill(dtbl);
sdata2.Fill(dtbl2);
ds.Tables.Add(dtbl);
ds.Tables.Add(dtbl2);
dtbl.PrimaryKey = new DataColumn[] { dtbl.Columns["id"] };
dtbl2.PrimaryKey = new DataColumn[] { dtbl2.Columns["id"] };
DataRelation dr = new DataRelation("provcomp",
ds.Tables["dtbl"].Columns["id_prov_comp"],
ds.Tables["dtbl2"].Columns["id"]);
ds.Relations.Add(dr);
ds.AcceptChanges();
Thanks in advance.
The primary keys are ok - you already successfully created them with the following lines:
dtbl.PrimaryKey = new DataColumn[] { dtbl.Columns["id"] };
dtbl2.PrimaryKey = new DataColumn[] { dtbl2.Columns["id"] };
The problem is (as indicated by the exception) in the DataRelation. DataRelation is the equivalent of the database FK (foreign key). The signature of the used constructor looks like this:
public DataRelation(
string relationName,
DataColumn parentColumn,
DataColumn childColumn
)
I think what you did wrong is specifying the wrong columns - parentColumn should point to the table being referenced while childColumn - the table referencing it.
Simple change it to:
DataRelation dr = new DataRelation("provcomp",
ds.Tables["dtbl2"].Columns["id"],
ds.Tables["dtbl"].Columns["id_prov_comp"]);
and if they are really related correctly, the problem should be solved.
Edit: In case the parent table does not contain all the keys from the child table, you might consider using this constructor overload and pass createConstraints = false, which will prevent the exception, but note that some child records will not to find their parent record.
DataRelation dr = new DataRelation("provcomp",
ds.Tables["dtbl2"].Columns["id"],
ds.Tables["dtbl"].Columns["id_prov_comp"],
false);
The problem here is your data. You're adding a primary key to each table and then attempting to link them with a foreign key.
However, from the error message you are receiving, it sounds like you are attempting to link dtbl to an id from dtbl2 which does not exist.
I would try to do a SQL query to find out where your problem records lie and then choose to either add a "parent", change the parent or delete the record.
SELECT * FROM componentes WHERE id_prov_comp NOT IN (SELECT ID FROM proveedores )
The parent column (that is primary key) must come before the child column (that is foreign key). Try this:
DataRelation dr = new DataRelation("provcomp",
ds.Tables["dtbl2"].Columns["id"],
ds.Tables["dtbl"].Columns["id_prov_comp"]);
The case that a column such as the ID column on a table will auto increment. This simply means that the next insert into the table will have an ID that is one more then the previous one and therefore all ID's will be unique.
However, if you delete a row from the table, the table will auto increment as if the row had not been deleted at all. The result is a gap in the sequence of numbers. This is normally not that much of an issue, but you may want to reset the auto increment field.
PatientNo is primary key and here is the code what I have tried so far
private void txtincr()
{
int a;
if (textPatientNo.Text == "")
{
if (con.State == 0)
{
con.Open();
}
string Query= "select PatientNo from PatientRegistration";
SQLiteDataAdapter DataAdapter = new SQLiteDataAdapter(Query,con);
DataSet PatientDataSet = new DataSet();
DataAdapter.Fill(PatientDataSet);
if (PatientDataSet.Tables[0].Rows.Count != 0)
{
a = PatientDataSet.Tables[0].Rows.Count;
a = a + 1;
textPatientNo.Text = Convert.ToString(a);
textPatientName.Focus();
}
else
{
textPatientNo.Text = "1";
textPatientName.Focus();
}
con.Close();
}
}
When you have declared the column as INTEGER PRIMARY KEY, you don't need to do anything because the database automatically uses the next value after the largest value in the table.
When you have declared the column as INTEGER PRIMARY KEY AUTOINCREMENT, the current counter is stored in the sqlite_sequence table. You can simply modify it by updating that table.
You can try using the SQL Query
DBCC CHECKIDENT('TABLENAME',RESEED,initialvalue);
Can I have a clustered key on DataTable in C# ?
There's a requirement in my code to have constraints for possible combinations of 3 columns to be unique .....
What you need is really a unique constraint on your DataTable. Clustered keys are a SQL Server on-disk feature and not applicable to a DataTable.
Check out MSDN doc on DataTable constraints:
The UniqueConstraint object, which can be assigned either to a single column or to an array of columns in a DataTable, ensures that all data in the specified column or columns is unique per row. You can create a unique constraint for a column or array of columns by using the UniqueConstraint constructor.
So try something like this:
// this is your DataTable
DataTable custTable ;
// create a UniqueConstraint instance and set its columns that should make up
// that uniqueness constraint - in your case, that would be a set of *three*
// columns, obviously! Adapt to your needs!
UniqueConstraint custUnique =
new UniqueConstraint(new DataColumn[] { custTable.Columns["CustomerID"],
custTable.Columns["CompanyName"] });
// add unique constraint to the list of constraints for your DataTable
custTable.Constraints.Add(custUnique);
And that should do the trick for you!
To make your columns enforce a UNIQUE constraint you could use
DataTable dt = new DataTable();
dt.Columns.Add("UniqueColumn");
dt.Columns["UniqueColumn"].Unique = true;
Solution two
If you want some combination of the values in some columns to have unique value, you can try this.
DataTable dt = new DataTable();
dt.Columns.Add("UniqueColumn1");
dt.Columns.Add("UniqueColumn2");
dt.Columns.Add("UniqueColumn3");
dt.Columns.Add("NormalColumn");
string
value1 = string.Empty,
value2 = string.Empty,
value3 = string.Empty,
value4 = string.Empty;
//Logic to take values in string values variables goes here
DataRow[] founded = dt.Select("UniqueColumn1 = '"+ value1+
"' and UniqueColumn2 = '"+value2+
"' and UniqueColumn3 = '"+value3+"'");
if (founded.Length > 0)
// Message to say values already exist.
else
// Add a new row to your dt.
In this code you check the data present in DT to enforce uniqueness
I am using a datatable created by program. In this datatable i want to insert values in some specified columns.
Initially I am inserting primary key values leaving remaining columns null, when I am querying datatable with recently inserted value in Primary column to update same row, I am facing error Missing operand after ID operator
Can any one tell me the exact issue.
I am trying following code:
dt.Rows.Add(1);
int insertedValue = 1;
DataRow[] dr = dt.Select("ID = '" + insertedValue.toString() + "'");
And the table structure after entring primary value is as follows.
ID Volumn1 Volumn2 volumn3
--------------------------------------
1
You can do this more cleanly with LINQ and make this a strongly typed operation.
Something like:
dt.Rows.Add(1);
int insertedValue = 1;
var result =
dt.AsEnumerable().Where( dr => dr.Field<int>( "ID" ) == insertedValue );
Working example:
DataTable dt = new DataTable();
dt.Columns.Add( "ID", typeof( int ) );
dt.Rows.Add( 1 );
var result = dt.AsEnumerable().Where( dr => dr.Field<int>( "ID" ) == 1 );
You can simply format the selection string as shown below:
DataRow[] dr = dt.Select(string.Format("ID ='{0}' ", insertedValue));
Feel free to let me know if this works for you.. Thanks
You do not need ' ' in your filter.
I think this should work:
DataRow[] dr = dt.Select("ID = " + insertedValue.toString());
By the way, reference System.Data.DataSetExtensions
If you are looking for a specific row and your datatable has a primary key you could use the Find method and target the primary key which would return just the row you want rather than an array:
DataRow foundRow = dt.Rows.Find([INSERT SEARCH PARAMETER HERE]);
if(foundRow != null)
{
TO SET A STRING EQUAL TO A FOUND VALUE:
string str = foundRow["COLUMN NAME / INDEX];
OR IF YOU ARE INSERTING A VALUE YOU CAN USE IT LIKE THIS:
foundRow["COLUMN NAME / INDEX"] = NEW VALUE;
}
select column of row
dt.Rows[0].Field<string>("MyColumnName")