Entity Framework to Auto Increment Non Identity Column / Concurrency Issue - asp.net-mvc-4

I have two tables:
CREATE TABLE [dbo].[Customers] (
[CustomerId] INT IDENTITY (1, 1) NOT NULL,
CONSTRAINT [PK_dbo.Customers] PRIMARY KEY CLUSTERED ([CustomerId] ASC)
);
CREATE TABLE [dbo].[Campaigns] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[CustomerId] INT NULL,
[CampaignId] INT NULL,
CONSTRAINT [PK_dbo.Campaigns] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Campaigns.CampaignId is unique to every CustomerId; therefore, it cannot be an identity. So from my web app, I need to auto increment the CampaignId upon Campaign creation. In the past, I've had to obtain a lock in a single transaction to obtain the next highest and issue an insert. How would I accomplish the same thing in EF without having to worry about or effectively manage concurrency?
In the Campaign controller, I have this (UserContext is a static helper class that retrieves the user's current CustomerId and db is my DbContext):
public ActionResult Create(Campaign campaign)
{
if (ModelState.IsValid)
{
int customerId = UserContext.customerId;
int maxCampaignId = db.Campaigns.Where(c => c.CustomerId == customerId).Max(c => c.CampaignId);
campaign.CampaignId = maxCampaignId + 1;
db.Campaigns.Add(campaign);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(campaign);
}
But wouldn't this risk duplicate values per CustomerId in a high concurrency environment?
EDIT:
I forgot to mention that a guid was not an option. The Ids have to be integers.
EDIT 2:
I forgot to mention that there is also a Users table which can have the same CustomerId. Users can create multiple Campaigns with the same CustomerId which causes the potential for concurrency issues.

You may want to look into a HiLo pattern, or just use Guid.NewGuid() instead of incrementing.
See: HiLO for the Entity Framework
What's the Hi/Lo algorithm?

Related

EF6 throws concurrency exception when trying to add new entry with identity column

I'm generating entity models from my database with EF6. I created two test tables. One table has an Identity column, and the other table doesn't. Here are the tables:
CREATE TABLE [dbo].[TestNoIdentity]
(
[ID] INT NOT NULL,
[DTStamp] DATETIME NOT NULL,
[Note] VARCHAR(255) NULL,
PRIMARY KEY CLUSTERED ([ID] ASC, [DTStamp] ASC)
);
CREATE TABLE [dbo].[TestIdentity]
(
[ID] INT IDENTITY (1, 1) NOT NULL,
[DTStamp] DATETIME NOT NULL,
[Note] VARCHAR(255) NULL,
PRIMARY KEY CLUSTERED ([ID] ASC, [DTStamp] ASC)
);
Test code:
using (TestEntities entities = new TestEntities())
{
// This works
var entry1 = new TestNoIdentity();
entry1.ID = 1;
entry1.DTStamp = DateTime.Now;
entry1.Note = "No Identity";
entities.TestNoIdentity.Add(entry1);
entities.SaveChanges();
// This doesn't work
var entry2 = new TestIdentity();
entry2.DTStamp = DateTime.Now;
entities.TestIdentity.Add(entry2);
entities.SaveChanges(); //optimistic concurrency exception
// This query works
// entities.Database.ExecuteSqlCommand("INSERT INTO [dbo].[TestIdentity] ([DTStamp]) VALUES ('1/1/2021 12:00:00 PM')");
return entities.ID.ToString();
}
Why is it throwing a concurrency exception? There are no other users or duplicated instances of the entity.
The message from the exception:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
Without IDENTITY EF doesn't have to fetch back the ID, and that's where it's failing. You've got a DATETIME column in your PK, and DATETIME only has precision of about 3ms, so comparing the stored value with the generated value may fail. Change it to DATETIME2 to better match the precision of .NET's DateTime, or trim your .NET DateTime to the nearest second.

Write to table without primary key using Entity Framework

I have a table and I want to insert entries with entity framework. By design, the table cannot have a meaningful primary key.
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
GO
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
GO
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
GO
The table contains a track list of charge carriers and storage locations. The three non-id fields contain information about which forklift moved the charge carrier, and when. The initial entry for each charge carrier, created by the system, only contains an unload date and both IDs. When a forklift picks up something, a new entry should be created with only the three fields ID_ChargeCarrier, PickedUpOn and TransportedByDevice being set. As the forklift unloads the charge carrier, the entry should be updated with the unload date and the ID of the storage location where the piece was transported to.
ID_ChargeCarrier must always be filled. For each of those IDs, there can only be one single entry with ID_Storage set to NULL, as defined by IX_OnForklift. A charge carrier can appear on the same storage multiple times.
I could make the combination of ID_ChargeCarrier, ID_Storage and PickedUpOn the primary key, but that also doesn't work, because MS-SQL doesn't allow PKs with a nullable column.
As you can see, there is no other meaningful primary key. I strictly don't want to introduce an otherwise-superfluous ID column just to make EF happy.
How can I make the insert work anyways through Entity Framework?
From the comments I see this is dealing with a legacy system.
What code would be considered the "owner" of this data, and how many other places (code, systems, reports, etc.) "touch" this data? Do you foresee needing to ever be querying against this data via EF?
If you just need to insert rows based on an event and don't care to ever query against this data via EF (at least for the foreseeable future) then I'd suggest merely inserting via a raw SQL statement and being done with it. New code for other areas may be starting to leverage EF, but "being consistent for consistency's sake" is never an argument I make. :)
The table design is poor. If this re-factoring needs to rely on this table, and the other touch-points are manageable then I would be arguing to re-design the table into something like:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
EventTypeId INT,
EventOn DATETIME2(2) NOT NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
Where EventTypeId reflects a Pickup or DropOff and EventOn is the Date. This would accommodate a PK/unique constraint across ID_ChargeCarrier, EventTypeId, and EventOn.
Heck, throw a PK column in, and re-factor TransportedByDevice to a FK to save space as I'm guessing this table will house a significant # of records. Porting existing data into a new structure shouldn't pose any issue other than processing time.
Or at a minimum keeping the same compatible structure, appending a proper PK into the table. For example you can use:
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
ID_ChargeCarrierStorage INT IDENTITY(1,1) NOT NULL
/w a PK constraint on the new identity column. This should be able to be appended without a table re-create. However, I expect that this table could be quite large so this would reflect a rather expensive operation that should be tested and scheduled accordingly.
EF needs a key defined to determine a unique row identifier. It doesn't even need to be declared as a PK in the database, though it is still restricted to using non-nullable fields. If these are records that you will be going to throughout the life of the system I would strongly recommend using a DB structure that accommodates a legal PK. I have had tables bound to entities that did not have PKs defined, but these were strictly transient staging tables where I loaded data from an external source like Excel, wired up some entities to the table to process the data and move relevant bits along to a permanent table.
EF just needs an Entity Key. It doesn't have to map to a real database Primary Key.
And you should put an unique index on the mapped fields in the database (or risk poor performance and wierd behavior).
In SQL Server unique indexes can have nullable columns. And you can map non-nullable Entity Properties to nullable database columns.
Here's an example using that table definition:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace Ef6Test
{
class ChargeCarrier_Storage
{
[Key()]
[Column(Order =0)]
public int ID_ChargeCarrier { get; set; }
[Key()]
[Column(Order = 1)]
public int ID_Storage { get; set; }
[Key()]
[Column(Order = 2)]
public DateTime PickedUpOn { get; set; }
public DateTime UnloadedOn { get; set; }
public string TransportedByDevice { get; set; }
}
class Db : DbContext
{
public Db(string constr) : base(constr) { }
public DbSet<ChargeCarrier_Storage> ChargeCarrier_Storage { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
class MyDbInitializer : IDatabaseInitializer<Db>
{
public void InitializeDatabase(Db context)
{
var sql = #"
drop table if exists dbo.ChargeCarrier_Storage;
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NULL,
PickedUpOn DATETIME2(2) NULL,
UnloadedOn DATETIME2(2) NULL,
TransportedByDevice NVARCHAR(50) NOT NULL,
--CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID) ON DELETE CASCADE,
--CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID) ON DELETE CASCADE,
CONSTRAINT CCS_OneNotNull CHECK (PickedUpOn IS NOT NULL OR UnloadedOn IS NOT NULL),
CONSTRAINT CCS_OnForklift CHECK (ID_Storage IS NULL AND PickedUpOn IS NOT NULL OR ID_Storage IS NOT NULL)
)
CREATE CLUSTERED INDEX IX_ChargeCarrier_Storage ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier)
CREATE UNIQUE INDEX IX_OnForklift ON dbo.ChargeCarrier_Storage (ID_ChargeCarrier, ID_Storage) WHERE ID_Storage IS NULL
";
context.Database.ExecuteSqlCommand(sql);
}
}
class Program
{
static string constr = "server=.;database=ef6test;integrated security=true";
static void Main(string[] args)
{
Database.SetInitializer<Db>( new MyDbInitializer());
using (var db = new Db(constr))
{
var f = new ChargeCarrier_Storage();
f.ID_ChargeCarrier = 2;
f.ID_Storage = 2;
f.PickedUpOn = DateTime.Now;
f.TransportedByDevice = "SomeDevice";
db.ChargeCarrier_Storage.Add(f);
db.SaveChanges();
}
using (var db = new Db(constr))
{
var c = db.ChargeCarrier_Storage.First();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
Eventually I "solved" the problem by completely re-doing the table. I removed a lot of the clutter and added a primary key.
CREATE TABLE dbo.ChargeCarrier_Storage
(
ID_ChargeCarrier INT NOT NULL,
ID_Storage INT NOT NULL,
ID_User INT NULL,
StoredOn DATETIME2(2) NOT NULL DEFAULT GetDate(),
CONSTRAINT PK_ChargeCarrier_Storage PRIMARY KEY (ID_ChargeCarrier, ID_Storage, StoredOn),
CONSTRAINT FK_ChargeCarrier_Storage_to_Storage FOREIGN KEY (ID_Storage) REFERENCES Storage(ID),
CONSTRAINT FK_ChargeCarrier_Storage_to_ChargeCarrier FOREIGN KEY (ID_ChargeCarrier) REFERENCES ChargeCarrier(ID)
)
GO
I consider a forklift to be it's own "storage", so the loading and unloading is fully abstracted away now.

SQL Update Query - An aggregate may not appear in the set list of an UPDATE statement

I'm trying to write a query that updates tbl8_update_transactions HID field (where it's null) with the primary key value (HID) that's highest in HOLIDAY_DATE_TABLE. I get the following error
"An aggregate may not appear in the set list of an UPDATE statement"
I've read that I need to accomplish this using a subquery, but need help. Thanks
USE BillingUI;
UPDATE tbl8_update_transactions
SET tbl8_update_transactions.HID = MAX(HOLIDAY_DATE_TABLE.HID)
FROM HOLIDAY_DATE_TABLE
WHERE tbl8_update_transactions.HID = NULL;
Update: Tried the proposed solution
UPDATE tbl8_update_transactions
SET HID = h.maxHID
FROM (select max(HOLIDAY_DATE_TABLE.HID) as maxHID from HOLIDAY_DATE_TABLE) h
WHERE tbl8_update_transactions.HID IS NULL;
Unfortunately this affects 0 rows/doesn't work. I think this is because HID is a foreign key (in tbl8_update_transactions). The real issue seems to be my C# methodology for inserting the records into the table (it inserts the row without populating the foreign key). I'd like to handle it with triggers rather than C# code. My tables are as follows.
USE BillingUI;
CREATE TABLE HOLIDAY_DATE_TABLE
(
HID INT IDENTITY PRIMARY KEY,
TABLE_NUMBER nchar(2) NOT NULL,
HOLIDAY_DATE nchar(8) NOT NULL,
FIELD_DESCRIPTION nVARchar(43) NULL,
);
USE BillingUI;
CREATE TABLE tbl8_update_transactions
(
TID INT IDENTITY PRIMARY KEY,
TABLE_NUMBER nchar(2) NOT NULL,
HOLIDAY_DATE nchar(8) NOT NULL,
FIELD_DESCRIPTION nVARchar(43) NULL,
HID int,
FOREIGN KEY (HID) REFERENCES HOLIDAY_DATE_TABLE (HID)
);
I think this might solve the null foreign key issue if I can get help with it
CREATE TRIGGER tbl8_ins
ON HOLIDAY_DATE_TABLE
FOR INSERT
AS
BEGIN
INSERT INTO tbl8_update_transactions
SELECT * FROM HOLIDAY_DATE_TABLE
WHERE HID = MAX(HID);
END
In case you want to see my C# code that performs the insert successfully, but doesn't populate the foreign key
public ActionResult Create()
{
return View();
}
//
// POST: /Table8/Create
[HttpPost]
public ActionResult Create(HOLIDAY_DATE_TABLE holiday_date_table, tbl8_update_transactions tbl8_update_transaction)
{
if (ModelState.IsValid)
{
db.HOLIDAY_DATE_TABLE.Add(holiday_date_table);
db.SaveChanges();
db.tbl8_update_transactions.Add(tbl8_update_transaction);
db.SaveChanges();
return RedirectToAction("../Billing/HolidayDateTable");
}
return View(holiday_date_table);
}
YOu can write the query like this:
UPDATE tbl8_update_transactions
SET HID = h.maxHID
FROM (select max(HOLIDAY_DATE_TABLE.HID) as maxHID from HOLIDAY_DATE_TABLE) h
WHERE tbl8_update_transactions.HID IS NULL;
I find it confusing to use a from clause and not have the main table mentioned there. I prefer writing this as:
UPDATE ut
SET HID = h.maxHID
FROM tbl8_update_transactions ut CROSS JOIN
(select max(HID) as maxHID from HOLIDAY_DATE_TABLE) h
WHERE ut.HID IS NULL;
I guess, your proposed code is correct, just missed SELECT
UPDATE tbl8_update_transactions
SET HID = (SELECT h.maxHID
FROM (select max(HOLIDAY_DATE_TABLE.HID) as maxHID from HOLIDAY_DATE_TABLE) h
WHERE tbl8_update_transactions.HID IS NULL);
SELECT is missing.
You wrote
MAX(HOLIDAY_DATE_TABLE.HID) FROM HOLIDAY_DATE_TABLE
You probably meant
SELECT MAX(HOLIDAY_DATE_TABLE.HID) FROM HOLIDAY_DATE_TABLE

Primary key consists of a foreign key and a identity and should reset identity under a condition

create table Linq_TB
{
url_id int NOTNULL,
Pg_Name nvarchar(50) NOTNULL,
URL nvarchar(50) NUTNULL,
CONSTRAINT Linq_id PRIMARY KEY (url_id,DBCC Checkident(Linq_TB,RESEED,0) case url_id not in(select URL_Id from URL_TB ))
}
I want to make a table which it's primary key is Linq_id and gets it's value from both the url_id and identity with start from 1 and increments 1 by 1. url_id is a foreign key. For example if url_id is 1, linq_id's will be 11, 12, 13,... and I also want to reset linq_id identity when the url_id changes.
What should the query be? The query above doesn't work, why?
Thanks in advance
Well, a constraint contains conditions and not code to be executed. You should consider using a stored procedure for your task and also a homegrown method of assigning IDs.
However, it is not a common practice to have your primary keys 'pretty' or formatted, as there is no real benefit (except maybe for debugging purposes maybe).
I do not recommend executing DBCC whenever your url_ID changes. This has a great negative impact on performance.
Why don't you leave the IDs like they are?
You can do this with the following table and trigger definitions:
CREATE TABLE Linq_TB
(
url_id INT NOT NULL,
Linq_id INT NOT NULL,
Pg_Name NVARCHAR(50) NOT NULL,
URL NVARCHAR(50) NOT NULL,
CONSTRAINT PK_Link_TB PRIMARY KEY (url_id, Linq_id),
CONSTRAINT FK_URL_TB_URL_ID FOREIGN KEY (url_id) REFERENCES URL_TB (url_id)
)
GO
CREATE TRIGGER tr_Linq_TB_InsertUpdate
ON Linq_TB
INSTEAD OF INSERT
AS
INSERT INTO Linq_TB
SELECT i.url_id,
ISNULL(tb.Linq_id, 0)
+ row_number() over (partition by i.url_id order by (select 1)),
i.Pg_Name, i.URL
FROM inserted i
LEFT OUTER JOIN
(
SELECT url_id, MAX(Linq_ID) Linq_id
FROM Linq_TB
GROUP BY url_id
) tb ON i.url_id = tb.url_id
GO
The CREATE TABLE defines your columns and constraints. And, the trigger creates the logic to generate a sequence value in your Linq_id column for each url_id.
Note that the logic in the trigger is not complete. A couple of issues are not addressed: 1) If the url_id changes for a row, the trigger doesn't update the Link_id, and 2) deleting rows will lead to gaps in the Linq_TB column sequence.

NHibernate Mapping a Table Were the Primary Key is Also a Foreign Key

I have 2 tables as follows:
create table Users
(
UserId int primary key identity not null
)
create table UserExternalKeys
(
UserIdRef int primary key not null,
ExternalKey varchar(50) unique not null
)
alter table UserExternalKeys
add constraint fk_UsersExternalKeys_Users
foreign key (UserIdRef)
references Users (UserId)
Each user can have a 0 or 1 external keys. Things are setup this way because adding a nullable unique column to SQL Server does not allow for more than 1 null value.
Based on Ayende's post, it seems like this could be handled using a <one-to-one> mapping. However, this would require the UserExternalKeys table to have its own primary key.
The new schema would look something like this:
create table Users
(
UserId int primary key identity not null,
ExternalKeyRef int null
)
create table UserExternalKeys
(
UserExternalKeyId int primary key identity not null,
ExternalKey varchar(50) unique not null
)
alter table Users
add constraint fk_Users_UsersExternalKeys
foreign key (ExternalKeyRef)
references UserExternalKeys (UserExternalKeyId)
I think this would work, but it feels like I would only be adding the UserExternalKeyId column to appease NHibernate.
Any suggestions?
If a user can have 0 or 1 external keys why not design the tables as:
create table Users
(
UserId int primary key identity not null
ExternalKey varchar(50) null
)
and use one of the known workarounds for this problem. If you're using SQL Server 2008 you can use a filtered index. If you're using an earlier version you can use a trigger, an indexed view (2005), or the nullbuster workaround.
You could also keep your original schema and map the relationship as one-to-many from Users to UserExternalKeys. Map the collection as a private member and expose access to it through a property:
private IList<UserExternalKeys> _externalKeys;
public string ExternalKeys
{
get
{
if (_externalKeys.Count() == 1)
{
return _externalKeys.ElementAt(0).ExternalKey;
}
else
{
// return null or empty string if count = 0, throw exception if > 1
}
}
set
{
if (_externalKeys.Count() == 0) { // add key and set value }
else { // set value if count = 1, throw exception if > 1 }
}
}