Write to table without primary key using Entity Framework - sql

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.

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.

How to update the nested tables in sql using gorm?

Here the code is written in Go. I am using two tables where one table has a foreign key that refers to the other table's primary key. Let's say I have a database as following struct defined:
type User struct{
ID uint `gorm:"primary_key;column:id"`
Name string `gorm:"column:name"`
Place place
PlaceID
}
type Place struct{
ID uint `gorm:"primary_key;column:id"`
Name string `gorm:"column:name"`
Pincode uint `gorm:"column:pincode"`
}
And the sql schema is:
create table place(
id int(20) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
pincode uint(20) NOT NULL,
PRIMARY KEY (id),
)
create table user(
id int(20) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
place_id uint(20) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (place_id) REFERENCES place(id)
)
Now while inserting in user by gorm as:
place := Place{Name:"new delhi",Pincode:1234}
user := User{Name: "sam", Age: 15, Place: place}
err = db.Debug().Create(&user).Error
//It inserts to both user and place table in mysql
//now while updating to name in user table as Samuel and place as
//following
place := Place{Name:"mumbai",Pincode:1234}
err = db.Debug().Model(&User{}).Where("id =?",
1,).Update(&user{Name:"Samuel",Place:place}).Error
It updates the row in user table but creates a new row in place table.But it should update the matching row in place table and not create a new one
Is there any way to do it? Here I am not using auto migrate function to create db tables.
The answer to your question should be sought in a relations or Association Mode.
The example below shows how to add new associations for many to many, has many, replace current associations for has one, belongs to
db.Model(&user).Association("Place").Append(Place{Name:"mumbai",Pincode:1234})
Or you can replace current associations with new ones:
db.Model(&user).Association("Place").Replace(Place{Name:"mumbai",Pincode:1234},Place{Name:"new delhi",Pincode:1234})
Probably It's creating a new row because you didn't set the ID on Place{Name:"mumbai",Pincode:1234}.

Entity Framework to Auto Increment Non Identity Column / Concurrency Issue

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?

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 }
}
}

I want generate INSERT which include my Primary Key (not generated) by NHibernate. How to map Id column for this?

I have table CUSTOMER where CUSTOMER_ID is primary key.
Table
CREATE TABLE [CUSTOMER](
[CUSTOMER_ID] [int] NOT NULL,
[START_DATE] [varchar(30)] NOT NULL,
CONSTRAINT [PK_CUSTOMER] PRIMARY KEY CLUSTERED ( [CUSTOMER_ID] ASC .....
Mapping
public class CustomerMap : ClassMap<Customer> {
public CustomerMap()
{
WithTable("CUSTOMER");
Id(x => x.CustomerID, "CUSTOMER_ID");
Map(x => x.Name, "NAME");
}
}
I need to handle assigning of the CustomerID property manually. For example I created new Customer with CustomerID = 777 and name "stackoverflow".
When I call method Session.SaveOrUpdate(customer);
I want NHibernate to generate me SQL like this:
INSERT INTO [CUSTOMER] ([CUSTOMER_ID] ,[NAME]) VALUES (777,'stackoverflow')
Unfortunately I'm getting errors.
Main issue here is that Nhibernate tries to generate ID for me. But I want to save my entity exactly with 777.
I think there should be way to setup mapping for Id() some another way so this will allow me do what I want.
Please help.
Depending on how you do your NHibernate mappints (fluent or hbm) you can setup your ID property to manually assign the id. I believe the default is to let the server assign the id.