I have another question kind of relative to this but I'd like to separate them for clarity reasons.
I stumbled upon a problem where I couldn't insert a new row in a table because it only had one column and that column was incrementally increased and PK.
However, creating a new object of that Set in Entity Framework was no trouble at all.
var admin = new Administrator {};
context.Administrator.AddObject(admin);
context.SaveChanges();
int adminId = admin.adminId; //This would give me the new value
How does this work?
When you commit your changes, Entity Framework performs an INSERT into the table associated with the Administrator entity set, for each added entities.
As the PK is auto incremented in the database, EF knows that it doesn't need to provide it, but retreive it after the INSERT. It then updates the Administrator entity with it's now available (and database generated) PK.
It's classic Object relational Mapping job, I hope I understood your question?
Here is the exact SQL query sent by EF to do the Insert job.
insert [dbo].[Entities] default values
select [Id]
from [dbo].[Entities]
where ##ROWCOUNT > 0 and [Id] = scope_identity()
It inserts a default row in the table, then select the new row's Id.
Related
This is doing my head in. I have a pretty simple model class like this (generated by EF Core Power Tools):
In the Azure SQL database, I have the following foreign key relationship in place which associates a Sighting record with to every MachineLearningTaggedImage record:
ALTER TABLE [dbo].[MachineLearningTaggedImage] WITH CHECK
ADD CONSTRAINT [FK_MachineLearningTaggedImage_Sighting]
FOREIGN KEY([SightingId]) REFERENCES [dbo].[Sighting] ([SightingId])
GO
ALTER TABLE [dbo].[MachineLearningTaggedImage]
CHECK CONSTRAINT [FK_MachineLearningTaggedImage_Sighting]
GO
I am then trying to do a very run of the mill insert, with the SightingId value populated (because I want the newly inserted MachineLearningTaggedImage record to link to an existing Sighting record that already exists in the database):
var newImageTag = new MachineLearningTaggedImage
{
SightingId = sighting.SightingId
};
_db.MachineLearningTaggedImages.Add(newImageTag);
Save();
But the insert fails with the following error:
The MERGE statement conflicted with the FOREIGN KEY constraint "FK_MachineLearningTaggedImage_Sighting". The conflict occurred in database "MyDatabase", table "dbo.Sighting", column 'SightingId'.
I'm puzzled because my existing value of SightingId (3398670) definitely exists in the Sighting table as an existing record which I want to link to.
But for some reason, EF Core or SQL won't let me do this insert.
Now if I go directly into a SQL query window (outside of EF Core), I can successfully insert the new MachineLearningTaggedImage record with a SightingId value = 3398670 - no problem at all. It works..
What am I running into? Is it something related to me having only populated the SightingId value, but the actual Sighting child / related object property is still null when I attempt to insert? I thought you could simply populated an ID of a related object and EF Core would be happy with that.
This is Entity Framework 6.1.3
The SQL Server table has a two-column composite key.
ID INT Identity(1,1) NOT NULL
VERSION INT NOT NULL
Inserting a new record works because I don't set the ID on my object; I only set the VERSION.
So a new record would look like this:
ID VERSION
1 1
Perfect! The database generates the ID because the column is configured with Identity and my model is decorated with [DatabaseGenerated(DatabaseGeneratedOption.Identity)].
But now I need to insert another row with the same ID but a different VERSION; hence the composite key. So I would expect the second row to be:
ID Version
1 1
1 2 <- second row has same ID and different version
I do need this to work both ways because there is the scenario where a new ID should be auto-generated by the database, and the other scenario where I have the same ID but a different VERSION.
The Problem:
Because my Code-First model has the ID configured with DatabaseGeneratedOption.Identity, when I set the ID property on my object, my SaveChanges generates the insert statement without the ID!
(Diagnostic Tools in VS shows that Entity Framework generated this statement)
ADO.NET: Execute Reader "INSERT [dbo].[T1]([Version], ... VALUES (#0, ...)
Note the omission of ID. Because I explicitly set the ID on my object, I expected to see this statement instead.
INSERT [dbo].[T1]([ID], [Version], ... VALUES (#0, #1, ...)
That's what I'm trying to accomplish.
The question is:
How can I make Entity Framework include that ID column in its generated insert statement in an elegant way?
I don't want to use a stored procedure or hard code a SQL statement or hack the insert statement by 'squeezing in' the column.
If there is no way, I know that I would have to remove the use of Identity altogether and define my own IDs, which I'm trying to avoid.
Also, my SaveChanges() already makes use of SET IDENTITY_INSERT ON/OFF so that isn't any problem.
Here is the relevant part of my model: (I omitted other properties)
[Key, Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Key, Column(Order = 1)]
public int VERSION { get; set; }
One avenue that I've explored was to reset my DbContext with a twist in OnModelCreating, but that didn't make a difference.
Of course, in that revision I did remove the DatabaseGenerated decorator off my ID property in the class. I inserted this into OnModelCreating:
if (this.AllowIdentityInsert)
{
modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
else
{
modelBuilder.Entity<T1>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
If I could successfully control the model by changing the ID property to DatabaseGeneratedOption to None before my SaveChanges, then this could work and be an elegant solution.
Has anyone run into this situation and found a good solution?
Thanks so much for your input or suggestions.
Generally you don't want to use an identity column in that manner but I suppose if you are using a composite key you could. The problem that you will be faced with to insert your second record is that you will have to turn IDENTITY_INSERT on and off. So thinking of the SQL of it here is an example to show you what has to be done to accomplish the task.
IF OBJECT_ID('tempdb..#TblName') IS NOT NULL
BEGIN
DROP TABLE #TblName
END
CREATE TABLE #TblName (
ID INT IDENTITY(1,1) NOT NULL, Version INT NOT NULL
)
INSERT INTO #TblName (Version) VALUES (1)
SET IDENTITY_INSERT #TblName ON
INSERT INTO #TblName (ID, Version) VALUES (1,2)
SET IDENTITY_INSERT #TblName OFF
SELECT *
FROM
#TblName
A more typical design is to actually maintain a log table via a trigger and store the history in it. Because in that table it wouldn't need the identity column simply another INT.
There are a few more 2 table designs to get around the limitation but you may also want to look into creating SQL SEQUENCE https://msdn.microsoft.com/en-us/library/ff878058.aspx and instead of using IDENTITY on the ID column retrieving a SEQUENCE when you need it and always inserting the value. If you use a SEQUENCE you get the added benefit of being able to add another IDENTITY column that will be a local table ID which is typically recommended rather than relying solely on the composite key.
Okay here is (to me) a very interesting way of doing getting around your IDENTITY issue and maintaining a "incremented version". You can use an Update able View instead of directly using your table. You would use 2 SEQUENCES one for ID and one for VersionId and then to get Version you would use ROW_NUMBER() in the view. You could expand this solution by adding INSTEAD OF INSERT/UPDATE trigger to handle setting of the IDS more automatically but I don't generally like triggers. Anyway, here is to me an interesting solution:
CREATE TABLE dbo.yourTable (
TableId INT NOT NULL IDENTITY(1,1)
,Id INT NOT NULL
,VersionId INT NOT NULL
,Col VARCHAR(100) NOT NULL
,PRIMARY KEY (Id, VersionId)
)
GO
CREATE SEQUENCE dbo.SEQ_yourTableIdBy1
START WITH 1
INCREMENT BY 1;
GO
CREATE SEQUENCE dbo.SEQ_yourTableVersionIdBy1
START WITH 1
INCREMENT BY 1;
GO
CREATE VIEW dbo.yourTable_v
AS
SELECT
Id
,VersionId
,Version = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId)
,Col
,LatestVersion = CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Id ORDER BY VersionId DESC) = 1
THEN 1 ELSE 0 END
FROM
dbo.yourTable
GO
--New Record
INSERT INTO dbo.yourTable_v(Id, VersionId, Col)
VALUES (NEXT VALUE FOR dbo.SEQ_yourTableIdBy1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'A')
SELECT * FROM dbo.yourTable_v
--Change To Existing Record
INSERT INTO dbo.yourTable_v(Id, VersionId, Col)
VALUES (1, NEXT VALUE FOR dbo.SEQ_yourTableVersionIdBy1, 'B')
SELECT * FROM dbo.yourTable_v
link showing how it works http://rextester.com/GBHG23338
To Make Entity Framework believe the view is a table you may need to change the Key definition and the Entity Type here is a msdn blog on the subject. https://blogs.msdn.microsoft.com/alexj/2009/09/01/tip-34-how-to-work-with-updatable-views/
Benefits:
this isn't going to break if 2 people try to submit simultaneously etc.
Entity Framework will think this is a normal table once you fake it out slightly by following the link.
While it will have VersionId incremented across all records it will still present a nice Version + 1 for use in your application.
And you can easily add a latest version column for use in your application/queries.
I am facing a problem at the moment, and I would like a quick solution for this.
I already found another answers about this issue but none had helped, I had read about INSERT INTO and INSERT ON DUPLICATE KEY UPDATE, but I think it doesn't really help me.
So the thing is, I have some Migrations enabled that Add new columns to existing tables, the thing is that the existing records on table prior to Migration get records with empty values even on existing columns (because I am changing the order of the columns on the new table migrated)
I am doing a SP that it's purpose is to fill the new Table migrated, according to Inserts or Updates.
I want to insert new records always except in the case that I found an existing record on the table with empty columns (column Id - primary key - is filled)
The "pseudo code" (mixing sql tables knowledge and c# syntax) would be something like this:
int i = 0;
foreach(Record item in importing_data_table)
{
if(i < tableMigrated.Length && tableMigrated[i].Column IS NULL)
{
UPDATE tableMigrated[i] SET Column = item.Column
}
else
{
INSERT INTO item
}
i++;
}
NOTE: Deleting all the rows from the tableMigrated before inserting is not possible, because there are foreign keys to that table. I got the following error trying that approach:
- The DELETE statement conflicted with the REFERENCE constraint "FK_blabla.Testing_testingTest.tableMigrated_Id".
NOTE2: Maybe my only option is using Cursors?
Thank you so much for your help in advance!
In your Stored Procedure, use the Merge Statement. Merge provides the functionality to INSERT based on your insert condition, otherwise UPDATE. Here is an example
MERGE dbo.FactBuyingHabits AS Target
USING (SELECT CustomerID, ProductID, PurchaseDate FROM dbo.Purchases) AS Source
ON (Target.ProductID = Source.ProductID AND Target.CustomerID = Source.CustomerID)
WHEN MATCHED THEN
UPDATE SET Target.LastPurchaseDate = Source.PurchaseDate
WHEN NOT MATCHED BY TARGET THEN
INSERT (CustomerID, ProductID, LastPurchaseDate)
VALUES (Source.CustomerID, Source.ProductID, Source.PurchaseDate)
Take a look at Technet - Inserting, Updating, and Deleting Data by Using MERGE
Probably sounds like a silly question, but there is an aspect about it I would like to know:
I'm working with objects that have a Guid-property for PrimaryKeys, which gets auto-generated in the database. I am using Entity Framework, Code First. If I do a Console.WriteLine with this property before saving, the value is
00000000-0000-0000-0000-000000000000.
After using Add and SaveChanges in the context, if I do a Console.WriteLine again with the same property, I have a value:
615f98eb-4ced-422a-877f-b9caa6f2b91f
Obviously, the object in memory has been updates. But I want to know how. The Guid is genereated in the database, so what happens?
Does the Guid-property of the object simply get updated from the database through EF, or does EF reload the entire object into memory after saving it in the database?
I would like to know, because that will determine how I design NUnit-tests in the same project.
Your EF object is updated when you call domainContext.SaveChanges(); Your new Id is generated by SQL database and value of Id is return value from DB. It is same for data types int, Guid and similar.
EF does not only submit an Insert/Update statement, at the same time it does a get statement to retrieve the generated primary key. In fact it is one single query. Your entity's primary key is then updated with the retrieved one. No magic behind this.
That's also one of the reason why batch updates / inserts are not supported. Every entity has to be updated / inserted on its own.
This is a query that is being executed when inserting an entity with a computed int primary key:
insert [dbo].[TestTable]
([Name])
values ('myname' /* #0 */)
select [ID]
from [dbo].[TestTable]
where ##ROWCOUNT > 0
and [ID] = scope_identity()
As you can see, the insert statement is followed by a select statement retrieving the computed columns (in this case ID). If there are more computed columns they're all selected here.
We are using Entity Framework 4.0 and we have an entity that is mapped to stored procedures provided by our DBA. The Insert, Update, and Delete functions in the mapping details all have their own stored procedures.
When using entities that are mapped to tables I am able to add a new entity, call dataContext.SaveChanges(); and then the new entity I instantiated automatically has its ID property populated with the value from the identity column in the database.
How can this be accomplished when the entity is mapped to stored procedures? Does the INSERT stored procedure have to do something special and/or do I have to do something special on dataContext.SaveChanges();?
Example
Traditional way
var newCustomer = new Customer
{
Name = "Fred",
Age = 24
};
// newCustomer.Id is null
dataContext.Customers.Add(newCustomer);
dataContext.SaveChanges()
// newCustomer.Id is what database identity column was set to.
Mapped to stored procedures.
var newCustomer = new Customer
{
Name = "Fred",
Age = 24
};
// newCustomer.Id is null
dataContext.Customers.Add(newCustomer);
dataContext.SaveChanges()
// newCustomer.Id is null
If you are using Identity column in database make sure that your stored procedure contains:
SELECT Scope_Identity() AS Id
after calling INSERT
Also make sure that PK in your entity mode is correctly configured with StoreGeneratedPattern set to Identity (should be automatically if you used Update from database)
I believe your DB needs to use ##identity or insert with a NewID() and return the Identity/NewID value back to you via the stored procedure. You technically could select from the database for the record you inserted, but that is a very questionable way to do it as; you don't know if the records was inserted (unless the SP failed at .Net), you may not know if duplicated records exists, or even if the data was changed after the insert but before the select. When in doubt I always highly recommend talking to your DBA about the best approach to your specific needs based your DBAs design.
Updates:
If he returns you the PK value, you should be able to do a standard select from the table to populate the entity like from e in context.entities where e.pkcolumn = spkeyreturned select e.
If he returns you ALL the data back, and can guarantee the data won't change, you might be able to create a new entity, populate it will all the data and use the Attach method. I personally wouldn't do that, but it is an option. Attaching and Detaching Object in Entity Framework.