Migrate from NHibernate ids (hilo) to SQL auto ids in SQL Server - c#

Is there a way to migrate existing database with all tables and relations to use SQL Server auto ids instead of Nhibernate (hilo) ids?
We have a .NET application which uses NHibernate. But the problem is, we are running out of int.
I know that this requires tables recreation with new ones which have ids set as auto incremented. Is there a easy way to migrate. For example some sort of query which will replicate tables, keep relations, but now with SQL Server ids instead of hilo ids. Biggest problem of hilo, it's using shared ids, which makes situation worse.
For example, we have a database of 3 tables:
dbo.Users
dbo.RegistrationResults
dbo.UserNotes
Tables:
dbo.Users
Id int (Primary)
Email nvarchar(255)
RegistrationResultFk int (Foreign Key)
dbo.RegistrationResults
Id int (Primary)
ValidationOutcome nvarchar(255)
dbo.UserNotes
Id int (Primary)
Message nvarchar(255)
RegistrationResultFk int (Foreign Key)
And data populated like this:
dbo.Users
Id
Email
RegistrationResultFk
1
test#gmail.com
2
4
test2#gmail.com
5
dbo.RegistrationResults
Id
ValidationOutcome
2
Awaiting confirmation
5
Confirmed
dbo.UserNotes
Id
Message
RegistrationResultFk
3
it's a test
2
6
it's a test 2
5
We want data after migration to look like:
dbo.Users
Id
Email
RegistrationResultFk
1
test#gmail.com
1
2
test2#gmail.com
2
dbo.RegistrationResults
Id
ValidationOutcome
1
Awaiting confirmation
2
Confirmed
dbo.UserNotes
Id
Message
RegistrationResultFk
1
it's a test
1
2
it's a test 2
2

I suggest you, to minimize impact, use Sequences that are equivalent of autoincrement fields but are stored outside the table.
Below simple example for a table
Create the sequence
This code creates a sequence.
With SSMS you can also navigate to Database -> Programmability -> Sequence ==>
right-click New Sequence.
Full Syntax.
CREATE SEQUENCE dbo.UserId
AS INT
START WITH 1234
INCREMENT BY 1
;
Read the syntax carefully to better set up your sequences, for example, you may want to set up the CACHE to increases performance minimizing the IOs required to generate sequence numbers
Get ID
For getting the ID you must issue a Raw Query with NHibernate.
NHibarnate Reference
NEXT VALUE
public int GetNextUserId(Session session)
{
var query = session.CreateSQLQuery("SELECT NEXT VALUE FOR dbo.UserId");
var result = query.UniqueResult();
return Convert.ToInt32(result);
}

Related

How to overcome duplicate ID while making transactions?

I have a live web application(.NET), in which I have a facility for making transactions. When more no. of users make any transactions at the same time, duplicate/same transaction ID gets generated for all the users who're all making transactions. Is there any way to avoid creating same ID ? I tried the following solutions but nothing helps.
1.Mutex
2.Table lock(SQL)
3.Generating Transaction ID at the time of inserting into the table.
Use a database to generate the ID. Some possible examples:
SQL Server has an auto increment feature. Oracle has a sequence feature
This will ensure your ID to be unique.
You mention SQL, so you have a database accessible.
Option 1
SQL Server already has an auto-increment feature that is both guaranteed to generate a unique ID and is efficient.
If you want to "add letters to that ID", the simplest solution is to add a separate varchar field that contains those letters, and then format them for display as a single number in your application (either by using a SQL query to do so or string.Format.
ID | IDText Application Display
------------------ -----------------------
1 | MyLabel 1-MyLabel
2 | MyLabel 2-MyLabel
3 | FooBar 3-FooBar
4 | SomeText 4-SomeText
So, in the above, ID would be an auto-increment int field and IDText would be a varchar (or char if you want a fixed length).
As you can see, even if the same string such as "MyLabel" were added at the same time, you would get a different ID to append to it so you in effect have a unique ID.
Option 2
Use a stored procedure to contain the following logic in a single transaction with BEGIN TRANSACTION/END TRANSACTION:
Query for the highest ID
Add 1 to that ID
Create a new record with the new ID
Return that ID
The transaction will guarantee that the number cannot be duplicated. This is less efficient for creating IDs, but more efficient at looking them up than the 2-column approach.

Creating object counters in Entity Framework and Sql Server

Note 1: I REPHRASED THE QUESTION. It now consists of Suppliers and Orders, instead of Cars and Parts.
Note 2: THIS PROBLEM IS HYPOTHETICAL.
My goal is to understand how to create object counters.
For regulatory requirements, I need TO SEQUENTIALLY NUMBER EACH Order for each of the suppliers.
I'm Using 'Entity Framework` with Sql Server.
In my hypothetical example, I have a Supplier class and an Order class.
Each supplier has Orders. Each order has a product and a quantity. Meaning, it states which product was ordered from the supplier and how many of it.
I need to be able to create counters, like an auto incremented number, to count the orders FOR EACH supplier.
For regulatory reasons, each supplier must sequentially number its orders, in the order of creation, and using an integer only.
When we examine an Order, We should know by its OrderCountForSupplier column, what was its order of creation (a DateTime / TimeStamp column is insufficient by the regulatory authorities. They require such a counter).
For simplicity of this question, an order cannot be deleted (it's status can change, but it cannot be deleted).
It's very important for me to have a solution which includes the technical/programming way, not only theoretic way.
I've made a diagram in order to explain my problem in the most clear way possible:
I have a way that might work, and would be glad to hear feedback.
I'm thinking of an external table/tables, to hold the counters. Something like:
Supplier Order Counters Table
| SupplierId | OrderCountForSupplier
------------------------
| 54654 | 3
| 78787 | 2
| 99666 | 4
Would I need a trigger in order to increment the OrderCountForSupplier counter on each insertion, for each supplier?
If not - how can this incremental be done in a safe way ? (without for example, two processes in a race condition to get the next counter and increment it, which could eventually result in a duplicate Order Count).
And another note:
Can this be done Entity Framework wise? if not - a Sql Server solution will be respected.
First answer, the example in the question has changed after it was written.
You say that is it OK to have gaps in the Part IDs, because "some parts might be deleted along the way".
So, what's the difference between your example:
Car PartID
54654 1
54654 2
54654 3
78787 1
78787 2
99666 1
99666 2
99666 5
99666 7
And this variant:
Car PartID
54654 1
54654 2
54654 3
78787 4
78787 5
99666 6
99666 7
99666 8
99666 9
In the second variant each part has some ID that is unique for each car (it is also globally unique as well, but it doesn't matter). In the second variant PartID specifies the order in which parts were inserted into the table, same as in the first variant.
So, I'd use a simple IDENTITY column:
Parts
PartID int IDENTITY NOT NULL (PRIMARY KEY)
CarLicenseNum int NOT NULL (FOREIGN KEY)
PartName varchar(255)
Update for Supplier-Order example
The most important bit in the updated question is "regulatory reasons". It answers the question why would you want to do such unnatural thing. "Regulatory" and efficiency are often opposite.
Essentially, it means that you have to use serializable transaction isolation level when inserting a new row and calculating the next number in the sequence. It will hurt concurrency/throughput, but it will guarantee consistency and "be safe" in multi-user environment.
I don't know how to do it in Entity Framework, it should be possible. But, again, for "regulatory reasons" I'd put this logic in the stored procedure in the DB and make sure that ordinary users don't have write access to the Orders table directly, but have rights only to execute this dedicated stored procedure. You can replicate the logic of this stored procedure in the EF code, but the database itself will be open to changes done through other applications, which may not follow the regulatory requirements.
You can implement it using the separate table, which stores the latest sequence number for each supplier, or you can read the last maximum sequence number on the fly. If each supplier has only few orders, then this separate table with latest values of counters would be comparable to Orders table and you would not gain much. In any case, having a proper index is the key. Getting the latest counter value would be one seek in the index.
Here is an example of stored procedure without using an extra table.
Make sure that Orders table has unique index on (SupplierId, OrderCountForSupplier). In fact, you must have this index even if you are using an extra table to enforce the constraint.
CREATE PROCEDURE [dbo].[AddOrder]
#ParamSupplierID int,
#ParamProductSerial varchar(10),
#ParamQuantity int,
#NewOrderID int OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
BEGIN TRY
DECLARE #VarMaxCounter int;
SELECT TOP(1) #VarMaxCounter = OrderCountForSupplier
FROM dbo.Orders
WHERE SupplierID = #ParamSupplierID
ORDER BY OrderCountForSupplier DESC;
SET #VarMaxCounter = ISNULL(#VarMaxCounter, 0) + 1;
INSERT INTO dbo.Orders
(SupplierID
,OrderCountForSupplier
,ProductSerial
,Quantity)
VALUES
(#ParamSupplierID
,#VarMaxCounter
,#ParamProductSerial
,#ParamQuantity);
SET #NewOrderID = SCOPE_IDENTITY();
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- TODO: handle the error
SET #NewOrderID = 0;
ROLLBACK TRANSACTION;
END CATCH;
END
GO
After investigating some possible approaches (see links at the bottom), I've came out with a very basic solution, with the help of #Vladimir Baranov.
I've ruled out using SqlServer triggers / Stored Procedures. They seemed hard to implement in conjunction with Entity Framework, and they seem to me like an Over-Kill in this scenario.
I've also ruled out the Optimistic Concurrency approach (using a concurrency token), because in this scenario, the counters cannot be updated simultaneously. They only get updated after a successful insertion to the orders table.
My orders table looks like that. I've added a unique constraint on the OrderId, SupplierId and OrderCountForSupplier trio, so insertion of the same order count for a supplier would fail.
I've indeed used a counters table, from which I can take the latest counter - for each of the suppliers.
Supplier Order Counters Table
| SupplierId | OrderCountForSupplier
------------------------
| 54654 | 3
| 78787 | 2
| 99666 | 4
These are the steps:
Get the current supplier orders counter.
Try insert a new order for the supplier, using the current counter + 1.
If the insertion goes ok => Increase the orders counter for this supplier, on the supplier counters table.
If insertion goes wrong, and we get an error stating the has been a violation of the constraint (same order count, which already exists):
Try 2 more times to get the current counter, and try inserting the order again.
The Code:
public class SupplierRepository
{
private MyContext _context;
private Supplier _supplier;
public SupplierRepository(int supplierId)
{
_context = new MyContext();
_supplier = context.Suppliers.Single(x => x.SupplierId == supplierId);
}
// Retrieve the latest counter for a supplier
public SupplierCounter GetCounter()
{
var counterEntity = _context.SupplierCounters.Single(x => x.SupplierId == _supplier.SupplierId);
return counterEntity;
}
// Adding a supplier
public void AddSupplier(Order order)
{
int retries = 3;
while (retries > 0)
{
SupplierCounter currentCounter = GetCounter();
try
{
// Set the current counter into the order object
_order.OrderCountForSupplier = currentCounter.OrderCountForSupplier;
_context.Add(order);
// Success! update the counter (+1) and then break out of the while loop.
currentCounter.OrderCountForSupplier += 1;
// I'M CALLING `SAVECHANGES` AFTER ADDING AN ORDER AND INCREASING THE COUNTER, SO THEY WOULD BE IN THE SAME TRANSACTION.
// THIS WOULD PREVENT A SCENARIO WHERE THE ORDER IS ADDED AND THE COUNTER IS NOT INCREMENTED.
_context.SaveChanges();
break;
}
catch (SqlException ex)
{
if (ex.Number == 2627) // Violating unique constraint
{
--retries;
}
}
}
}
}
Some useful links:
SQL Server Unique Composite Key of Two Field With Second Field Auto-Increment
Atomic Increment with Entity Framework
how to inc/dec multi user safe in entity framework 5
This is not a real world example. That's why you are struggling. For an example, A real world parts entity is lot more complicated than that. A real world part will have a ManufacturerId (BMW, Audi etc), PartNumber(B4-773284-YT), VehicleModelId (AUDI A4 etc), Description, ManufacturerYear so on and so forth. Usually when it comes to parts entities, we use a concatanated primary key on ManufacturerId and PartNumber.
Same with your car table. It's not a real world example too. Car entity should have a VIN number, which is unique. When you say each part is specific, you are not talking about Part entity. You are talking about PartInventory entity. PartInventory has a unique serial number (barcode) for each part. So every single part can be identified uniquely. When you attach a part to a vehicle, you are not just attaching a Part, you are actually attaching a PartInventory item, which is recognizable by a unique serial number.
Once the partInventory item is attached to a vehicle, it becomes a fitted part item of the vehicle. Which means the part gets transferred to VehicleParts table.
Unfortunately I see a lot of gaps in your vehicle industry domain knowledge. We develop systems to address real world problems. When you try to address hypothetical problems, you run in to this kind of issues. That leads to wasting lot of other peoples time who are trying to help you out.
First things first: it is not OK to change your question entirely! Delete this question and create a new one. Having said that ...
Answer of the current question:
Answers to hypothetical questions are just oppinion based and/or too broad (there is actually a flag for this - Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise.)!
My answer to the current question is: I do not see any benefit (or advantage or use) of the OrderCountForSupplier in the database! Creating such counter in the database makes adding and maintenance (in a multi-threaded environment) very complicated and error-prone.
I think the problem can be solved more easily with the help of EF (move the creation of the counters in the code) and a different design of the database:
in order to allow concurrent adding of Orders, create two columns - a GUID as the Order-PrimaryKey and a CreationDate of type DateTime. Filling those two columns from multiple threads is not a problem
when retrieving all Orders for a specific SupplierId, sort the result list in ascending order by CreationDate
when iterating over the result list using (for example) a for-loop, then the counter is the desired sequential counter
as an alternative to the EF solution, the creation of the sequential counter can stay in SQL - create a view or stored procedure for the Order items and use ROW_NUMBER to create the artificial sequential count, after grouping the items over SupplierId and sorting on CreationDate
Reading the database from multiple threads (and creating the counter in every thread) is again not a problem any more.
Answer of the first question:
You are almost there. You need to normalize your data model a little bit more. This is a common scenario in which you want to minimize redundancy of the data and at the same time still maintain a meaningful relation (without the use of triggers).
One possible solution would be to create a Car_has_Part-Table in order to represent the relation between a Car and a Part entity:
| Car_has_Part |
----------------
| PartId |
| CarId |
The primary key of the Car_has_Part table is a composite primary key consisting of CarId + PartId which is unique and at the same time you avoid data duplication.
In your example in the Parts table the Doors part is repeated for every Car. Using this intermediate table the data is not duplicated and you have a proper relation.
Your new data model could look like this:
| Car | | Car_has_Part | | Part |
------- ---------------- ----------
|CarId | | PartId | | PartId |
|Model | | | | Descr |
| etc. | | CarId | | etc. |
This model allow resp. covers the specified requirements:
I need to be able to create a counter, like an auto incremented
number, to count the parts for each car. Car 1, could have parts 1, 2,
3... and Car 2 would also have parts 1, 2, 5, 7... (some parts might be deleted along the way).
Select all PartId's from the Car_has_Part table over CarId.
Each part HAS to be counted separately for its related car. That's the
base requirement.
Same as above (without data duplication like in your example). Adding resp. removing a relation or modifying a part name has also become easier - you need to update only one row in the Parts table and the change is reflected for every car.
About the triggers question - you can only create a trigger with EF (using code first approach). Regarding execution - triggers are always executed in the database and EF can't control trigger execution (you can certainly enable/disable trigger using raw SQL queries, but if I understand your question correctly this is not what you want).

Cannot swap unique value on two rows with EF

I'm trying to swap the value in a unique column for two (or more rows). For example:
Before update:
Row 1 = 1
Row 2 = 2
After update:
Row 1 = 2
Row 2 = 1
I'm using Entity Framework. These changes take place on a single commit for the same context. However, I always get a unique constrain violation when I attempt this update. Is EF not using a transaction?
For completeness, here is a snippet of my table design:
[FeeSchemeId] UNIQUEIDENTIFIER NOT NULL,
[SortPosition] INT NOT NULL,
UNIQUE (FeeSchemeId, SortPosition)
I'm trying to update the 'SortPosition' column. The code is a bit complex to display here, but I can assure you that it is the same context with a single final commit. The error is only thrown when EF tries to write to the database.
UPDATE:
-
Using SQL Server Profiler I can see that EF is running separate UPDATE for each affected row. Should EF not be using a single transaction for one call to SaveChanges()?
-
UPDATE 2:
Turns out EF is using a single transaction after all. SQL Profiler was filtering it out.
You can't do it with 2 statements also with SQL Server. You need to use a third value
BEGIN TRANSACTION;
UPDATE MyTable Set Id = 200 where Id = 1;
UPDATE MyTable Set Id = 1 where Id = 2;
UPDATE MyTable Set Id = 2 where Id = 200;
COMMIT;
BTW, SQL Server profiler shows BEGIN TRANSACTION/COMMIT statements
An alternative trick I have used which doesn't rely on temporary values (which themselves risk violating uniqueness) is to issue a single UPDATE as in:
UPDATE MyTable
SET ID = case when id = 1 then 2 else 1 end
WHERE ID in (1, 2)
Unfortunately EF is not smart enough to generate those type of statements by itself.

View and Entity Framework data not right?

I have a view from some table when I select from the view in SQL Server Management Studio it works fine, but when I use Entity Framework to get the data from view it's different.
ReturnDbForTesEntities1 db = new ReturnDbForTesEntities1();
List<VJOBS2> list = new List<VJOBS2>();
list = db.VJOBS2.ToList();
Same number of records but last 2 rows are different.
I have table for job applicant applicant can apply for 2 jobs or more
ApplicantId ApplicantName JobId JobName
1 Mohamed 1 Developer
1 Mohamed 2 IT Supporter
but in list
ApplicantId ApplicantName JobId JobName
1 Mohamed 1 Developer
1 Mohamed 1 Developer
There is a subtle problem with views when used from Entity Framework.
If you have a table, do use it with EF, you need to have a primary key to uniquely identify each row. Typically, that's a single column, e.g. an ID or something like that.
With a view, you don't have the concept of a "primary key" - the view just contains some columns from some tables.
So when EF maps a view, it cannot find a primary key - and therefore, it will use all non-nullable columns from the view as "substitute" primary key.
I don't know what these are in your case - you should be able to tell from the .edmx model.
Let's assume that (ApplicantId, ApplicantName) are the two non-nullable columns that EF now uses as a "substitute" primary key. When EF goes to read the data, it will read the first line (1, Mohamed, 1, Developer) and create an object for that.
When EF reads the second line (1, Mohamed, 2, IT-Supporter), it notices that the "primary key" (1, Mohamed) is the same as before - so it doesn't bother creating a new object with those values read, but the primary key is the same, it hence must be the same object as it has already read before, so it uses that object instead.
So the problem really is that you can't have explicit primary keys on a view.
Either you can tweak your EF model to make it clear to EF that e.g. (ApplicantId, JobId) is really the primary key (you need to make sure those columns are both non-nullable) - or you need to add something like a "artificial" primary key to your view:
CREATE VIEW dbo.VJOBS2
AS
SELECT
ApplicantId, ApplicantName, JobId, JobName,
RowNum = ROW_NUMBER() OVER(ORDER BY JobId)
FROM
dbo.YourBaseTable
By adding this RowNum column to your view, which just numbers the rows 1, 2, ...., n, you get a new, non-nullable column which EF will include into the "substitute PK" and since those numbers are sequential, no two rows will have the same "PK" values and therefore none will erroneously be replaced by something that's been read from the database already.
FYI, I had to add ISNULL to get it to work for me, see the modification in the first line of this code example:
SELECT ISNULL(ROW_NUMBER() OVER(ORDER BY a.OrderItemID),0) as ident, a.*
FROM
(
SELECT e.AssignedMachineID, e.StartDate, e.OrderItemID, e2.OrderItemID AS doubleBookedEventID, e.StartTime, e.EndTime, e2.StartTime AS doubleBookedStartDateTime, e2.EndTime AS doubleBookedEndDateTime, DATEDIFF(MINUTE,e2.StartTime,e.EndTime) AS doubleBookedMinutes
FROM schedule e
INNER JOIN schedule e2
ON e.AssignedMachineID = e2.AssignedMachineID
and e.StartDate=e2.StartDate
AND e.schedID <> e2.schedID
AND e2.StartTime BETWEEN DATEADD(minute,1,e.StartTime) AND DateAdd(minute,-1,e.EndTime) where Coalesce(e.ManuallyOverrided,0)=0 and Coalesce(e.AssignedMachineID,0) > 0
) a

Multiple identity columns or a way to auto increment

I have a table (tOrder) that has the following structure in SQL Server 2008
orderID (int) - this is currently the primary key and the identity field.
name(varchar)
address(varchar)
groupID (int) - now this field i need to also auto increment, but at the same time i want to be able to insert values into.
My data would look something like:
1 - john - address1 - 1
2 - mary - address2 - 1
3 - mary -address3 - 2
4 - jane - address4 - 3
where order IDs 1 and 2 share the same group , while 3 and 4 are in their own.
Many orders can have same groupID, but when I insert an order of a new group, I would like the groupID to be auto populated with the next sequence number automatically, while at the same time allowing me to insert duplicate groupID for different orders if I need to.
Hope this makes sense.
How do I go about doing this? (I'm using c# in the back end, if that makes any difference)
I would create a new "groups" table with an identity to ensure uniqueness as follows:
create table tOrders(
orderID int PRIMARY KEY IDENTITY,
name varchar(30),
address varchar(60),
fkGroup int
);
create table tGroups(
groupID int PRIMARY KEY IDENTITY,
description varchar(50)
);
ALTER TABLE tOrders
ADD FOREIGN KEY (fkGroup) REFERENCES tGroups(groupID);
You would, of course have to either supply a groupID for the IDENTITY of a newly inserted tGroup (groupID) value.
This SQL Fiddle Example demonstrates one way of populating the tables.
One option would be to create a trigger on your torder table (not a fan of triggers, but given your criteria, can't think of another option).
CREATE TRIGGER tOrder_trigger
ON tOrder
AFTER INSERT
AS
UPDATE tOrder
SET groupid = (SELECT COALESCE(MAX(groupid),0) + 1 FROM tOrder)
FROM INSERTED AS I
WHERE I.groupid IS NULL
AND tOrder.orderid = I.orderid;
SQL Fiddle Demo
This checks if the inserted record has a NULL groupid using INSERTED, and if so, updates the table to the MAX(groupid) + 1, using COALESCE to check for NULL.

Categories