Add a non existing Primary Key in a SQL server table - c#

I want to insert data in a table that has integers as primary keys. The primary keys will be added by a wide range of random numbers. First I want to check if the primary key exists and then insert it.
Here is my code:
public static void Insert()
{
bool a=true;
while (a != false)
{
try
{
db.test.Add(new test() //insert into test table new data
{
Id = rand.Next(1, 10),
name = "test",
surname = "test"
});
db.SaveChanges();
a=false;
}
catch (Exception)
{
Console.WriteLine("Duplicate");
}
}
}
In the first iteration if the number is not duplicate I can successfully insert the new row in database. If in the first iteration it is duplicate entry after db.SaveChanges();i jump directly in catch. But the problem arises when in the second iteration the random number is not duplicate it still jumps to catch exception without executing the a=false line of code.
Any Idea?
Note: I am not interested on using GUIDs or any other data type.

I might have an easier solution for you if you don't NEED it to be a random number.
CREATE TABLE Persons (
ID int IDENTITY(1,1) PRIMARY KEY,
Surname varchar(255) NOT NULL,
Name varchar(255)
);
INSERT INTO Persons (Name,Surname)
VALUES ('Lars','Monsen');
This way you don't even need to do anything with the PRIMARY KEY, SQL Server will handle it for you!

The issue here is that once a duplicate key has been generated, it will fail to insert, so it will keep trying to insert it each time; to quote OP:
Yes exactly, when it detects a duplicate it keeps trying it even though the next number may not be a duplicate.
There are 3 main ways of solving this:
don't generate your own keys; sorry, but this is the simplest, most obvious, and most supportable mechanism here; IDENTITY is your friend
if you fail to insert something : remove that same something from your ORM context so you don't keep failing; there is presumably a db.test.Remove method that will suffice; you may also be able to just change the value in the last row, abusing the fact that you know it was the last row that failed: db.test.Last().Id = newId; - this is very odd though; everything gets weird if you change identity keys
or alternatively, don't keep re-using the data context after failure; assuming that db is now unusable after the exception, and create a new one for any subsequent operations; IMO this should be done by the same code that originally created the db-context, in a "redo from start" way

Related

I found two identical entities in the database with the same Primary Key

I'm using Entity Framework 6 with Code First on an SQL server. Today, with my greatest surprise, I received an exception Sequence contains more than one element on a a simple query by ID (in my domain, the primary Key of each object). After debugging, I found that in my database 2 identical entities with the same Primary Key exist.
Now, I have no idea how that could happen, but my biggest problem right now is how to solve the issue: I cannot just delete them both, since they are 2 users with important data associated to them. So I tried to remove just one, but I receive an exception due to the fact that some other object references this user (and again, I cannot delete those objects because they contain important data).
var users = _userService.GetAllBy().ToList();
var duplicatedUsers = users.Where(x => users.Count(y => y.Id == x.Id) > 1);
foreach (var user in duplicatedUsers)
{
try
{
dbContext.Users.Remove(user);
dbContext.SaveChanges();
}
catch (Exception e)
{
// it always enters here because of the foreign keys
}
}
Basically, since the 2 identical objects have the same foreign key, they also share the same relationships with the other related entities. Therefore, I cannot just simply delete one of them because that causes an exception. But I don't want to delete them both either, because that would cause data loss. Any suggestion?
If sql server you can use a window function to identify a row and delete it that way -- see How do I delete duplicate rows in SQL Server using the OVER clause? as an example. Alternatively, if the table has more columns that what is defined in the key, you can hopefully use the other columns to more uniquely identify the "duplicate" row.
If it is, say, Oracle, you can get the ROWID of the row (just do a select rowid, t.* from table_name_here t WHERE conditions_here) and then when you find the right rowid, just do a straight DELETE FROM table_name WHERE rowid = XYZ123

EF Code-first, how to insert primary key to another field when inserting?

This is my entity that I will insert into the database:
public sampleEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
[Required]
public string Ticket { get; set; }
}
Ticket is in the format like
string.Format("{0:yyyyMMdd}-{1}", DateTime.Now, PrimaryKey);
So when I add this entity to the context, primaryKey always is 0, so Ticket value always be '20170315-0'
For now my solution is
// first add row and save
SampleEntity sample = new SampleEntity {
Ticket=DateTime.Now.ToString("yyyyMMdd")
};
context.Samples.Add(sample);
context.SaveChanges();
// then find it out and update
var latest = context.Samples.OrderByDecending(p => p.PrimaryKey).First();
latest.Ticket += sample.PrimaryKey.ToString();
context.SaveChanges();
How can I set Ticket value according to primaryKey value when SaveChanges() without update?
You have DatabaseGeneratedOption.Identity option for primary key.
That means that only SQL Server know that ID, and that knowledge materializes only in the moment of the actual INSERT into database (as related column in database is some kind of IDENTITY column).
To understand that think, for example, of two applications which are inserting new records into database simultaneously - they will receive different keys, but you cannot know for sure which application receive which key.
Entity Framework will generate two requests for SaveChanges - first one is INSERT, and another one is SELECT to receive that generated key.
Only after that your code will know actual key and can be able to use it for your ticket calculations - so basically you cannot avoid another UPDATE with EF.
What you can do though is to change Primary Key type for something controlled by your code rather than by database - for example, random GUID; in this case you will know ID before insert and can use it in any way you want.
But using say GUID for primary key results in other complications, which in most cases won't worth it, like non-sequential insert results in often index rebuilds, still some probability of keys collision, more space to keep column etc.
Another option would be to have calculated column OR similar logic for ticket column in application, so you will have separate Date column and separate Id column, but for ticket you will either always apply concat logic whenever you need it, of create calculated column which will only return values (and thus will be read-only for database and EF).

C# WPF delete item with its primary key from database using datagrid

With LINQ, I'm trying to delete a selected row in datagrid from database (made with code first) using db.Dishes.Remove(Dish);
But when I delete the item and inserting a new one, primary key (id) of new item "jumps" a value.
E.g.
1 Shoes
2 Jeans //I delete this item
When adding a new Item
1 Shoes
3 T-Shirt //jumps a value for Id
I've tried with this too in my DBContext.cs
modelBuilder.Entity<Cart>()
.HasOptional(i => i.Item)
.WithMany()
.WillCascadeOnDelete(true);
But it's not working
Is there a better way to delete an item from database?
The thing is that when we use DELETE it removes the row from the table but the counter is not changed (if the deleted row has an auto increment PK) see DELETE vs TRUNCATE
.
So, if you want to reuse the key value then you could do something on the lines of:
1) Handle the Auto Increment part of Key in your code
2) If you have
access to DB or want to query it something on the lines of this will
might be of help (SQL Server) :
DBCC CHECKIDENT ('tablename', RESEED, newseed)
to do this from code you could after the delete do :
db.ExecuteCommand("DBCC CHECKIDENT('tablename', RESEED, newseed);")
where 'newseed' is the id of the deleted row.e.g if newseed is 0 then next insert will be 1 and if it is 10 then the insert will have 11. To get the new seed value you could also get the max id value residing in your db and then work from there. Better check out what approaches you can take if you decide to go down that road.
From Reset autoicrement in SQL Server and how to use it in code.
If your primary key is an auto integer, you cannot avoid this behavior. This is simply how the database works. If you want to control the int value, do NOT make it the primary key and do not use auto integer. Instead use uniqueidentifier as your primary key and make your int a normal field. Then when you create your new records, you need to have a robust mechanism to get the next index, lock it so nobody else can steal it, and then write your record.
This is not trivial in a multi-threaded environment! You should do some research on the topic and come up with a good scheme. Personally, I'd NEVER attempt to do this and would use a repeatable process to generate numbers that are non-sequential or unique to a thread.
The primary key has to be unique (by definition), and you have also defined it as an identity column.
So when you delete a row and create a new one, that new one will take the next available key (3 in your case).
If you don't want this behaviour you will have to manage the uniqueness of the primary key yourself.

Is it possible to get Identity Field value before saving it in entity framework

I have a customer and sales table
CUSTOMER
--------------
Id (int auto increment)
Name
SALES
---------------
Id (int auto increment)
CustomerId (int)
OrderTotal (decimal)
With Guid i can do this.
dbTransaction = dbContext.Database.BeginTransaction(isolationLevel);
var customer = new Customer()
{
Id = Guid.NewGuid(),
Name = "John Doe"
};
var sales = new Sales()
{
Id = Guid.NewGuid(),
CustomerId = customer.Id,
OrderTotal = 500
};
dbContext.SaveChanges();
dbTransaction.Commit();
How can i do this if my primary key is int (with DatabaseGeneratedOption.Identity)?
You cannot. The ID that goes into a IDENTITY column is generated by the database upon insertion, and all "tricks" to circumvent that and determine the ID yourself are probably flawed.
Short answer: If you want some say in generating an ID before you save, use a GUID (UNIQUEIDENTIFIER), or a SEQUENCE (if you're working with SQL Server 2012 or newer).
Why you should not compute the next free ID yourself:
Don't even consider running a query such as context.Customers.Max(c => c.Id) + 1 as a viable solution, because there's always the possibility that you have concurrent database accesses: another process or thread might persist a new entity to the same table after you've read the next "free" ID but before you store your entity. Computing the next free ID will be prone to collisions, unless your whole operation of getting the ID, doing something with it, and storing the entity with that ID were atomic. This would likely require a table lock in the DB, which might be inefficient.
(The same problem exists even when you use SEQUENCEs, a new feature introduced in SQL Server 2012.) (I was wrong; see end of answer.)
Possible solutions:
If you need to determine the ID of an object before you save it, then don't use the ID that goes in a IDENTITY column. Stay with a GUID, because you're extremely unlikely to get any collision with these.
There's no need to chose between one or the other: you can actually have your cake and eat it! Nothing stops you from having two ID columns, one that you determine externally (the GUID) and one that stays internal to the DB (the IDENTITY column); see the blog article "CQS vs. server generated IDs" by Mark Seemann for a more detailed look at this idea. Here's the general idea by example:
CREATE TABLE Foos
(
FooId INT IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
-- ^^^^^ assigned by the DBMS upon insertion. Mostly for DB-internal use.
Id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE DEFAULT (NEWID()),
-- ^^ can be dictated and seen by the users of your DB. Mostly for DB-external use.
…
);
CREATE TABLE FooBars
(
FooId INT NOT NULL FOREIGN KEY REFERENCES Foos (FooId),
-- use DB-internal ID in foreign key constraints ^^^^^
…
);
CREATE VIEW PublicFoos AS
SELECT Id, … FROM Foos;
-- ^^ publish the public ID for users of your DB
(Make sure you adhere to some convention for consistently naming internal and public ID field names.)
SEQUENCEs, a feature introduced in SQL Server 2012, are a possible alternative to having an IDENTITY column. They are automatically increased and you are guaranteed a unique number when getting the next free ID using NEXT VALUE FOR SomeSequence. One of the use cases mentioned on MSDN are:
Use sequences instead of identity columns in the following scenarios: […] The application requires a number before the insert into the table is made.
Some caveats:
Getting the next sequence value will require an additional roundtrip to the database.
Like identity columns, sequences can be reset / re-seeded, so there is the theoretical possibility of ID collisions. Best to never re-seed identity columns and sequences if you can help it.
If you fetch the next free sequence value using NEXT VALUE FOR, but then decide not to use it, this will result in a "gap" in your IDs. Gaps obviously cannot happen with regular (non-sequential) GUIDs because there is no inherent ordering to them.
As far as I know you can not get the ID before saving the changes in the database. The database creates the ID after the values are inserted in the database.
To add to it when you call .SaveChanges() then only it will write the changes to the database and only then the identity value will get generated.
You can get that value by a small hack.
Create a function in SQL Server something like this
CREATE FUNCTION fn_getIdentity(#tbl_name varchar(30))
AS
BEGIN
IF #tbl_name = 'Employee_tbl'
RETURN IDENT_CURRENT('Employee_tbl')
ELSE IF #tbl_name = 'Department_tbl'
RETURN IDENT_CURRENT('Department_tbl')
ELSE
RETURN NULL
END
Create an entity in your Entity framework to support this function and use it wherever you want.
Then use
var nextValue = dbContext.fn_getIdentity("Employee_tbl")
IDENT_CURRENT returns you last incremented value for an identity column. This doesn't mean MAX + 1 as if your previous transaction generated an identity value for this column but was rolled back then you will see next value that will be generated.
Please note, I didn't check syntax properly and this syntax is just to present an idea.
However I would go with solution provided by Stakx i.e. SEQUENCE if using SQL Server 2012 or above
else creating a table to implement functionality of SEQUENCE by reserving ID once generated permanently in a table.
We can indeed if your ID is in integer, using SQL. The following example is for PostreSQL, please feel free to adapt it for other servers and to edit this answer.
Create a virtual entity model for the database to wrap our query result and to have a fake DbSet<some virtual model> to use ef core extension method FromSqlRaw.
Define a virtual model:
public class IntReturn
{
public int Value { get; set; }
}
Now fake a DbSet<IntReturn> it will not be really created on server:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<IntReturn>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
Now we can get the currently used Id for Customers table in this example. The calling method is inside a Subclassed : DbContext, you'd what to instantiate your context to use it instead of this:
public int GetNextCustomerId()
{
//gets current id, need do +1 to get the next one
var sql = "Select last_value as Value FROM \"Customers_Id_seq\";";
var i = this.Set<IntReturn>()
.FromSqlRaw(sql)
.AsEnumerable()
.First().Value + 1;
return i;
}
Credits to:
https://erikej.github.io/efcore/2020/05/26/ef-core-fromsql-scalar.html
https://stackoverflow.com/a/18233089/7149454

how to use SCOPE_IDENTITY() not in insert

I want to get new id(Identity) before insert it. so, use this code:
select SCOPE_IDENTITY() AS NewId from tblName
but is get this:
1- Null
2- Null
COMPUTED COLUMN VERSION
You'll have to do this on the sql server to add the column.
alter table TableName add Code as (name + cast(id as varchar(200)))
Now your result set will always have Code as the name + id value, nice because this column will remain updated with that expression even if the field are changed (such as name).
Entity Framework Option (Less ideal)
You mentioned you are using Entity Framework. You need to concatenate the ID on a field within the same record during insert. There is no capacity in SQL (outside of Triggers) or Entity Framework to do what you are wanting in one step.
You need to do something like this:
var obj = new Thing{ field1= "some value", field2 = ""};
context.ThingTable.Add(obj);
context.SaveChanges();
obj.field2 = "bb" + obj.id; //after the first SaveChanges is when your id field would be populated
context.SaveChanges();
ORIGINAL Answer:
If you really must show this value to the user then the safe way to do it would be something like this:
begin tran
insert into test(test) values('this is something')
declare #pk int = scope_identity()
print #pk
You can now return the value in #pk and let the user determine if its acceptable. If it is then issue a COMMIT else issue the ROLLBACK command.
This however is not a very good design and I would think a misuse of the how identity values are generated. Also you should know if you perform a rollback, the ID that would of been used is lost and wont' be used again.
This is too verbose for a comment.
Consider how flawed this concept really is. The identity property is a running tally of the number of attempted inserts. You are wanting to return to the user the identity of a row that does not yet exist. Consider what would happen if you have values in the insert that cause it too fail. You already told the user what the identity would be but the insert failed so that identity has already been consumed. You should report to the user the value when the row actually exists, which is after the insert.
I can't understand why you want to show that identity to user before insert, I believe (as #SeanLange said) that is not custom and not useful, but if you insist I think you can do some infirm ways. One of them is
1) Insert new row then get ID with SCOPE_IDENTITY() and show to user
2) Then if you want to cancel operation delete the row and reset
identity (if necessary) with DBCC CHECKIDENT('[Table Name]', RESEED,
[Identity Seed]) method
Other way is not using the Identity column and manage id column by yourself and it must be clear this approach can't be work in concurrency scenarios.
I think perhaps you're confusing the SQL identity with a ORACLE sequence.
They work completely different.
With the ORACLE sequence you'll get the sequence before you insert the record.
With a SQL Identity, the last identity generated AFTER the insert in available via the SCOPE_IDENTITY() function.
If you really need to show the ID to the user before the insert, your best bet is to keep a counter in a separate table, and read the current value, and increment that by one. As long as "gaps" in the numbers aren't a problem.

Categories

Resources