Why is the unique constraint missing when filling the datatable? - c#

I have the following table in SQL server
projects
id(PK, int, not null)
name (varchar(255), not null)
public_key_token (varchar(50), null)
I have added a unique constraint to the name column using
ALTER TABLE dbo.projects
ADD CONSTRAINT name_unique UNIQUE (name);
which results in a Unique, Non-Clustered index on the table (trusting SSMS).
In the code I'm retrieving the table data using
using (SqlDataAdapter adapter = new SqlDataAdapter("select * from " + DbTabName, con))
{
using (DataTable table = new DataTable(DbTabName))
{
DataTable dt = adapter.FillSchema(table, SchemaType.Source);
PkColumns = dt.PrimaryKey.Select(c => c.ColumnName).ToList();
AutoIncrementColumns = dt.PrimaryKey.Where(c => c.AutoIncrement).Select(c => c.ColumnName).ToList();
UniqueColumns = dt.Columns.Cast<DataColumn>().Where(c => c.Unique).Select(c => c.ColumnName).ToList();
...
}
}
The PKs and AutoIncrement columns are OK but in UniqueColumns I only get the PK column again.
The name column arrives in C# without the Unique constraint.
Changing the SchemaType to Mapped did not alter the result.
Why do I lose this constraint on the way to C#? Am I missing something on the SQL Server side or in C#?
[UPDATE]
#Tim-Schmelter's answer only solves the problem half way.
Just adding the index did not work. Also adding the index when the PK on the id column exists doesn't work.
The only way I got it to work was delete the table, recreate it without any key and indexes and then add the unique index as in Tim's answer. However, after adding the PK for the id column once more I'm back to the old behaviour that only the id is listed as unique column.
This is really weird.

Use this to create a unique index or use the the gui of SSMS.
CREATE UNIQUE NONCLUSTERED INDEX [name_unique] ON [dbo].[projects]
(
name ASC
)
You have added a unique constraint not a unique index.
Your code now successfully retrieves the UniqueColumns(the single column name).

Related

how to get max id from a table using linq

I have a table Estimation which has an column EstimationNo,i am trying to get the max EstimationNo like this-
var result = cont.SalesEstimateCont.Where(x => x.Org_ID == CurrentOrgId);
var estimationMaxNo = result.Any() ? result.Max(x => x.EstimateNo) + 1 : 1;
var DigitalEstimate = new SalesEstimate()
{
EstimateNo=estimationMaxNo;
};
cont.Estimate.Add(DigitalEstimate );
cont.Savechanges();
but the problem is, if same table is saving by different users at same time its saving the same EstimationNo for both users. like- 10,10
Now, how to handle this issue..please give some solution.
Best strategy is to let db engine (I assume that it is SQL Server) handle incrementing of EstimateNo field. This can be done with identity specification which can be added to normal not primary key field also.
ALTER TABLE SalesEstimateCont drop column EstimateNo
go
ALTER TABLE SalesEstimateContadd Add EstimateNo int NOT NULL IDENTITY (1,1)
Please note: if you have existing data or some data should be modified, you may need some extra effort to achieve this (i.e with temp tables and by setting IDENTITY INSERT ON)
I got a simple answer.I just had to use transacationScope class.
and lock the resource table. like this-
using (TransactionScope scope = new TransactionScope())
{
cont.Database.ExecuteSqlCommand("SELECT TOP 1 * FROM Sales__Estimate WITH (TABLOCKX, HOLDLOCK)");
var result = cont.SalesEstimateCont.Where(x => x.Org_ID == CurrentOrgId);
var estimationMaxNo = result.Any() ? result.Max(x => x.EstimateNo) + 1 : 1;
var DigitalEstimate = new SalesEstimate()
{
EstimateNo=estimationMaxNo;
};
cont.Estimate.Add(DigitalEstimate );
cont.Savechanges();
}
If you can make EstimateNo an Identity column, that is the easiest/best way to fix this. If you can change this to a Guid, that would be another easy way to fix this as PK would be unique regardless of the user.
If you can't do either of these and you must take Max() manually, you might want to consider creating another table that stores the next available number there. Then you can create a new SqlCommnand with a Serializable transaction to lock the table, update the # by 1 and select it back. If two update commands hit at the same time, only one update will run and won't let go until that connection with Serializable transaction gets closed. This allows you to select the newly updated number before the other update runs and get the now "unique" next number.
You can OrderByDescending and then Take the the first record
var estimationMaxNo = result.OrderByDescending(x => x.EstimateNo).Take(1);
It can be done in a single command. You need to set the IDENTITY property for primary id
ALTER TABLE SalesEstimateCont ADD Org_ID int NOT NULL IDENTITY (1,1) PRIMARY KEY

LINQ DeleteOnSubmit without Primary key

I have the following code and I want to delete all rows from TABLE where the column 'id' IS NOT Primary Key.
#{
using (var db = new DataClassesDataContext())
{
var query = db.Table.Where(r => r.id == 2).ToList();
if (query != null)
{
foreach (var q in query)
{
db.Table.DeleteOnSubmit(q);
}
}
db.SubmitChanges();
}}
This throws a System.InvalidOperationException because the table has not Primary Key column.
How is it possible to do that without adding a primary key in the SQL Server database?
Thanks in advance
Open the LINQ Designer. Open the properties window for the table you want to delete a record from. Click on any of the columns in the entity you want to delete and you'll see a property labeled "Primary Key". Change the value to true for column you want to use as a primary key.
P/S:This will not set the primary key on your real table.

on delete remove values with foreign key

I have two tables with foreign key relationship (1 to many). How can I do that when I'll remove item from first table, it will delete automatically all values with it's foreign key from second table?
So when I'll remove item from 1 table it will remove all items from 2 table whih NameId = ID of 1 table
Depends on the database management system; with MS SQL Server, you can set a foreign key to "ON DELETE CASCADE" which does exactly what you're asking for.
http://www.techonthenet.com/sql_server/foreign_keys/foreign_delete.php
You could just write a function for that:
public void RemoveWithRelatedEntries(int table1ItemID)
{
using(var db = new dbEntities())
{
// get all entities of table 2
var tab2Entities = db.table2.Where(tab2Ent=>tab2Ent.table1Reference == table1ItemID);
// remove all those entities
foreach(table2Item item in tab2Entities)
{
db.table2.Remove(item);
}
// finally remove entity from table 1
var table1entitiy = db.table1.Find(table1ItemID);
db.table1.Remove(table1entity);
db.Save();
}
}
there are probably more elegant solutions but this was what came first to my mind.

C# Update my Database with a statment

How can I update my column values in a table, with the help of primary key ?
Or can I override somehow the primary key?
Primary key is unique so you cannot change it. If you want to update the values using primary key then it is possible. But you cannot update the primary key.
this question sounds like youre trying to do another insert rather than an update.
If you're doing
insert into table (col1, col2, col3) values ('primaryKeyValue', 'col2val', 'col3val')
and then you try and do the same this will fail because of the primary key constraint.
you should be doing
update table set col1 = 'newValue', col2 = 'newValue2' where 'primaryKeyValue' = 'primaryKeyValue'
Use update query :
update tablename set column1 = 'new1', column2 = 'new2', 'primaryKey' = 'newPKValue' where 'primaryKey' = 'PKValue';
just one thing to remember 'newPKValue' should not be duplicated.

DataRelation Insert and ForeignKey

I have a winforms application with two DataGridViews displaying a master-detail relationship from my Person and Address tables. Person table has a PersonID field that is auto-incrementing primary key. Address has a PersonID field that is the FK.
I fill my DataTables with DataAdapter and set Person.PersonID column's AutoIncrement=true and AutoIncrementStep=-1. I can insert records in the Person DataTable from the DataGridView. The PersonID column displays unique negative values for PersonID. I update the database by calling DataAdapter.Update(PersonTable) and the negative PersonIDs are converted to positive unique values automatically by SQL Server.
Here's the rub. The Address DataGridView show the address table which has a DataRelation to Person by PersonID. Inserted Person records have the temporary negative PersonID. I can now insert records into Address via DataGridView and Address.PersonID is set to the negative value from the DataRelation mapping. I call Adapter.Update(AddressTable) and the negative PersonIDs go into the Address table breaking the relationship.
How do you guys handle primary/foreign keys using DataTables and master-detail DataGridViews?
Thanks!
Steve
EDIT:
After more googling, I found that SqlDataAdapter.RowUpdated event gives me what I need. I create a new command to query the last id inserted by using ##IDENTITY. It works pretty well. The DataRelation updates the Address.PersonID field for me so it's required to Update the Person table first then update the Address table. All the new records insert properly with correct ids in place!
Adapter = new SqlDataAdapter(cmd);
Adapter.RowUpdated += (s, e) =>
{
if (e.StatementType != StatementType.Insert) return;
//set the id for the inserted record
SqlCommand c = e.Command.Connection.CreateCommand();
c.CommandText = "select ##IDENTITY id";
e.Row[0] = Convert.ToInt32( c.ExecuteScalar() );
};
Adapter.Fill(this);
SqlCommandBuilder sb = new SqlCommandBuilder(Adapter);
sb.GetDeleteCommand();
sb.GetUpdateCommand();
sb.GetInsertCommand();
this.Columns[0].AutoIncrement = true;
this.Columns[0].AutoIncrementSeed = -1;
this.Columns[0].AutoIncrementStep = -1;
You need to double click the relationship in the dataset designer, and select Cascade Updates. When your real SQL server generated PK values for your Person table are generated, it will automatically set the foreign key values in the address table as well.
You don't need to do any of that RowUpdated event stuff. Its built into the dataset functionality.
I had a similar problem, but my solution was a little different.
#Noel Kennedy: Your solution does not work with SQL Server 2005 CE, because it doesn't support multiple statements and the TableAdapter won't generate the refresh code needed to update the autoincrement columns in the parent table.
NOTE: You still need Cascade Updates in the relationship so the child tables get updated.
I also add a method in my TableAdapter, which is generic enough to just copy/paste in all your parent TableAdapters. The only thing that I change is the identity row type and index (if needed). I also add a query to the TableAdapter called GetIdentity(). You can add it to the TableAdapter in the dataset designer by adding a scalar query with sql="SELECT ##IDENTITY;"
Now the custom function is:
public int InsertAndRefresh(System.Data.DataTable dataTable)
{
int updated = 0;
System.Data.DataRow[] updatedRows = dataTable.Select("", "", System.Data.DataViewRowState.Added);
bool closed = (this.Connection.State == System.Data.ConnectionState.Closed);
if (closed)
this.Connection.Open();
foreach (System.Data.DataRow row in updatedRows)
{
updated+=this.Adapter.Update(new global::System.Data.DataRow[] { row });
decimal identity = (decimal)this.GetIdentity();
row[0] = System.Decimal.ToInt64(identity);
row.AcceptChanges();
}
if (closed)
this.Connection.Close();
return updated;
}
You want to call this on the parent first. Then do everything as usual (update parent and then children).
Cheers!

Categories