For some reason few of the SQL tables in my .Net Core project has a primary key column (varchar) value set by an after insert trigger. see the trigger below.
USE [MyDb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [Lookup].[MyTable_Insert] ON [Lookup].[MyTable] AFTER INSERT
AS
SET NOCOUNT ON
UPDATE Lookup.MyTable
SET ID = convert(varchar,ID_AUTO)
ID - Primary Key, varchar, not null
ID_AUTO - smallint, not null, Identity column
When Inserting a new record, I get the following error.
DbUpdateConcurrencyException: Database operation expected to affect 1
row(s) but actually affected 0 row(s). Data may have been modified or
deleted since entities were loaded
When I was using Entity Framework (Not the core), I was able to take care of the issue by adding the following line of code at the end of the AFTER INSERT trigger.
SELECT CAST(SCOPE_IDENTITY() AS varchar) AS ID
But when I converted my application to .net core (ef core), that line of code is causing another error as below.
InvalidOperationException: The database generated a null value for
non-nullable property 'IdAuto' of entity type 'MyTable'.
Ensure value generation configuration in the database matches the
configuration in the model.
Looking at the SQL profiler, here is the SQL statement I can see.
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Lookup].[MyTable] ([ID], [Active])
VALUES (#p0, #p1);
SELECT [ID_AUTO]
FROM [Lookup].[MyTable]
WHERE ##ROWCOUNT = 1 AND [ID] = #p0;
',N'#p0 varchar(5),#p1 varchar(3),#p0='9999',#p1='Y'
So I think EF Core ignore the SCOPE_IDENTITY and just uses the value of ID used to insert the record, to query it back.
Any help?
UPDATE:
I submitted a ticket to the EF Core team, and looks like they have confirmed it as an issue. Here is the link to the ticket.
Ticket
I submitted a ticket to EF Core team. They said they are not supporting these feature and the best course of action will be to make the ID_AUTO column as the Primary Key. So that's what I ended up doing.
I am facing a peculiar issue with loading a list of tables from a specific database (well rather a group of databases) while attached to the master database. Currently my query loads all of the databases on the server, then loops through those databases sending information back to the client via RAISERROR. As this loop is executing I need a nested loop to load all of the tables for the current database for later transmission as a SELECT once the query has completed. The issue I'm running into is that this will be executed as a single query inside of C# code. Ideally I would like to load everything in SQL and return it to the client for processing. For example:
WHILE (#dbLoop < #dbCount) BEGIN
-- Do cool things and send details back to client.
SET #dbName = (SELECT _name FROM dbTemp WHERE _id = #dbLoop);
-- USE [#dbName]
-- Get a count of the tables from info schema on the newly specified database.
WHILE (#tableLoop < #tableCount) BEGIN
-- USE [#dbName]
-- Do super cool things and load tables from info schema.
SET #tableLoop += 1;
END
SET #dbLoop += 1;
END
-- Return the list of tables from all databases to the client for use with SQLDataAdapter.
SELECT * FROM tableTemp;
This topic is pretty straight forward; I just need a way to access tables in a specified database (preferably by name) without having to change the connection on the SqlConnection object, and without having to have a loop inside of my C# code to process the same query on each database on the C# side. It would be more efficient to load everything in SQL and send it back to the application. Any help that can be provided on this would be great!
Thanks,
Jamie
All the tables are in the meta data you can just do a query against that and join to your list of schemas you want to look at.
SELECT tab.name
FROM sys.tables AS tab
JOIN sys.schemas AS sch on tab.schema_id = sch.schema_id
JOIN dbTemp temp on sch.name = temp.[_name]
This returns a list of the table to return back as a result set.
The statement USE [#dbName] takes effect AFTER it is run (usually via the GO statement.
USE [#dbName]
GO
The above 2 lines would make you start using the new Database. You cannot use this in the middle of your SQL or SP.
One other option which you can use is to use the dot notation, i.e., dbname..tablename syntax to query your tables.
double dot notation post
Okay, after spending all day working on this, I have finally come up with a solution. I load all the databases into a table variable, then I begin looping through those databases and send back their details to the client. After the database details themselves have been sent to the client via RAISERROR I then utilize sp_executesql to execute a new sub-query with the current database specified to get the list of tables for processing at the end of the primary. The example below demonstrates the basic structure of this process for others experiencing this issue in the future.
Thank you all once again for your help!
-Jamie
DECLARE #LoopCounter INT = 1, #DatabaseCount INT = 0;
DECLARE #SQL NVARCHAR(MAX), #dbName NVARCHAR(MAX);
DECLARE #Databases TABLE ( _id INT, _name NVARCHAR(MAX) );
DECLARE #Tables TABLE ( _name NVARCHAR(MAX), _type NVARCHAR(15) );
INSERT INTO #Databases
SELECT ROW_NUMBER() OVER(ORDER BY name) AS id, name
FROM sys.databases
WHERE name NOT IN ( 'master', 'tempdb', 'msdb', 'model' );
SET #DatabaseCount = (SELECT COUNT(*) FROM #Databases);
WHILE (#LoopCounter <= #DatabaseCount) BEGIN
SET #dbName = (SELECT _name FROM #Databases WHERE _id = #LoopCounter);
SET #SQL NVARCHAR(MAX) = 'SELECT TABLE_NAME, TABLE_TYPE
FROM [' + #dbName + '].INFORMATION_SCHEMA.TABLES';
INSERT INTO #Tables EXEC sp_executesql #SQL;
SET #LoopCounter += 1;
END
Code-first auto generates an insert procedure code as below for a table that has ProductID as primary key (identity column).
CREATE PROCEDURE [dbo].[InsertProducts]
#ProductName [nvarchar](max),
#Date [datetime],
AS
BEGIN
INSERT dbo.ProductsTable([ProductName], [Date])
VALUES (#ProductName, #Date)
-- identity stuff starts here
DECLARE #ProductID int
SELECT #ProductID = [ProductID]
FROM dbo.FIT_StorageLocations
WHERE ##ROWCOUNT > 0 AND [ProductID] = scope_identity()
SELECT t0.[ProductID]
FROM dbo.ProductsTable AS t0
WHERE ##ROWCOUNT > 0 AND t0.[ProductID] = #ProductID
END
GO
Could you please explain the code that handles the identity column? Also, if an insert procedure is to be manually written from scratch, would it be handled differently?
If for example I would remove this auto generated code, I would encounter one of the following errors:
Procedure ....expects parameter '#ProductID', which was not supplied
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
In the app, this is how I call the procedure which works fine until I try to mess with the code first auto generated SQL:
using (var db = new AppContext())
{
var record = new ProductObj()
{
ProductName= this.ProductName,
Date = DateTime.UtcNow
};
db.ProductDbSet.Add(record);
db.SaveChanges();
}
I guess there are two things to be explained here.
Why a SELECT statement when I insert stuff?
Let's first see what a regular insert by Entity Framework looks like. By "regular" I mean an insert without mapping CUD actions to stored procedures. The normal pattern is:
INSERT [dbo].[Product]([Name], ...)
VALUES (#0, ...)
SELECT [Id]
FROM [dbo].[Product]
WHERE ##ROWCOUNT > 0 AND [Id] = scope_identity()
So the INSERT is followed by a SELECT. This is because EF needs to know the identity value that the database assigns to the new Product to assign it to the entity object's Product.ProductId property and to track the entity. If for some reason you'd decide to do an update immediately after the insert, EF will be able to generate an update statement like UPDATE ... WHERE Id = #0.
When the insert is handled by a stored procedure, the sproc should return the new Id value in a way that looks like the regular insert. It expects to receive a one-column result set of which the column is named after the identity column. It should contain one row, the new identity value.
So that's why there is a SELECT statement in there, and why EF complains if you remove it. But, you might ask, does EF really need 7 lines of code to get an assigned identity value?
Why so much code?
Honestly, I have to speculate a bit here, because it isn't documented as far as I can find. But let's look at a minimal working version:
INSERT [dbo].[Products]([Name])
VALUES (#Name)
SELECT scope_identity() AS ProductId;
This does the job. It's even the standard example of many tutorials, including official ones, on mapping CUD actions to stored procedures.
But a database can be stuffed with triggers, constraints, defaults, etc. It's hard to predict their influence on the returned scope_identity() under the wide range of circumstances EF may encounter. So EF wants to guarantee that the returned value really belongs to the newly inserted record. And that a record has actually been inserted in the first place. That's why it adds the SELECT from the Product table, including the ##ROWCOUNT.
To implement these safeguards, a minimal version would be:
INSERT [dbo].[Products]([Name])
VALUES (#Name)
SELECT t0.[ProductId]
FROM [dbo].[Products] AS t0
WHERE ##ROWCOUNT > 0 AND t0.[ProductId] = scope_identity()
Same as in the regular insert.
That's as far as I can follow EF. It puzzles me a bit that this single SELECT apparently is enough for a regular INSERT but not for a stored procedure. I can't explain why there are two SELECTs in the generated code.
I have 3 tables in a SQL Server 2008R2 database, that I need to fill their records right after each other so I used transaction to do this job with no problem. basically I have 2 INSERT store procedure queries in middle of a transaction to insert records in these tables as the code below;
The transaction was handled in C# SqlTransaction class at ASP.NET.
The following procedures just used in middle of the transaction.
First Table:
ALTER PROCEDURE [INSERT_RESOURCE]
#docID int,
#resTitle nvarchar(500),
#resCategory nvarchar(100),
#resType nvarchar(50),
#resLink nvarchar(MAX),
#createdBy nvarchar(50),
#createdDateTime datetime
AS
BEGIN
INSERT INTO Resource
VALUES(#resTitle, #resCategory, #resType,
#resLink, #createdBy, #createdDateTime)
END
Second Table:
CREATE PROCEDURE [INSERT_RESOURCE_DOCUMENT]
#docName nvarchar(200),
#docSize nvarchar(50),
#docType nvarchar(50),
#docPath nvarchar(MAX),
#docTitle nvarchar(100),
#uploadBy nvarchar(50),
#uploadDateTime datetime
AS
BEGIN
INSERT INTO Document
VALUES(#docName, #docSize, #docType, #docPath,
#docTitle, #uploadBy, #uploadDateTime)
INSERT INTO Resource_Document --Third table
VALUES(
(SELECT TOP 1 ResourceID FROM Resource ORDER BY ResourceID DESC),
(SELECT TOP 1 DocID FROM Document ORDER BY DocID DESC)
)
The above procedures are work fine but the possible issue could be on the third procedure, that is using the last ID of the first two tables to insert data in the third table, but because of the last INSERT statement is using the SELECT TOP 1 query it might pick up the wrong id if at the same time someone else use the same transaction to add some values into the first two tables.
so I was wondering how can I resolve the issue in this transaction ?
is there any other ways that I can used in third store-procedure to get those ids from the first two tables ?
Your problem here is scope. You want to gain the last inserted value for that user, during that transaction. Your select top 1 queries break the scope of the user and may select the last inserted value for any user.
To remain in the user scope, take advantage of SQL's scoping methods. Convert all 3 of these actions into one single stored procedure, then use the SCOPE_IDENTITY() method to get the value that was last inserted into an identity column for this session/user. This will safely guarantee that users won't get each others' inserted values.
Read more here: http://msdn.microsoft.com/en-us/library/ms190315.aspx
The third script will definitely lead to an issue when two records are added at the same time.
I think you could place an after trigger (for every insert on Resource) and an after update trigger (for every insert on Document).
or you could join the above two tables (Resource & Document) and then create a trigger which adds the data to the third table (Resource_Document)
For reference - http://msdn.microsoft.com/en-us/library/ms189799.aspx
I am trying to use a stored procedure in Entity Framework that returns nothing.
I did the following:
Added a function (right click on stored procedure -> add -> function import-> Complex Type -> Get column information -> create New Complex-Type)
My function name: summarySP_Result. After building the project the entity class is not generated in Generated_code (BusinessAccount.web.g.cs)
But entity classes for tables and views are all created but nor for stored procedure.
Can anybody give the idea why it is not generated entity class in BusinessAccount.web.g.cs?
Update :
Let me confirm ReturnDataFromTemTable_result entity class created in your XXXXXX.web.g.cs class.
Like :
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/BizFramework.Web.Model")]
public sealed partial class ReturnDataFromTemTable_Result : Entity
{
-------------------
}
OK - here's the step-by-step way of doing this:
(1) add your stored procedure to the EDMX file (when you first create it, or later on by using Update model from database and picking that stored procedure)
(2) once you have the stored procedure in your model - use the Model Browser to add a Function Import :
(3) the next dialog that pops up is vitally important - you need to (1) define that the stored procedure returns a collection of complex types, then you need to (2) get the column info from that stored procedure to know what columns it will return, then (3) you tell Visual Studio to generate a new complex type based on that column info:
(4) once you've done that - you should now see the stored procedure in your conceptual model section in the Model Browser, and the newly generated complex type should show up there, too:
This is for Ross Brigoli
Try adding this line to the beginning of your stored procedure:
SET FMTONLY OFF
You can remove this after you have finished importing.
Source:-
Why can't Entity Framework see my Stored Procedure's column information?
If this is still unresolved, after you Add the Function Import, go to the Solution Explorer, right click your {name}.Context.tt file and do "Run Custom Tool". The method will now show up in your derived Context class.
This seems like a bug in Visual Studio 2012, which is what I am using, I haven't applied Update 1, I will try to see if that fixes it.
As Sandeep said,
EF doesn't support importing stored procedures which build result set from Dynamic queries or Temporary tables.
But you don't have to rewrite your whole SP.
Just write another one, with the same name, that returns the correct row format without using dynamic SQL or a temp table. Then use the EF SP adding function, which will now auto generate the complex type.
Edit: It's actually easier to make a comment at the top of the SP that immediately selects the desired row with all the data types specified with CASTS. When you need to import the SP into EF, just uncomment the code.
e.g.
CREATE PROCEDURE myProc ()
AS
BEGIN
-- uncomment the following row to import:
-- SELECT CAST( 0 AS int) AS column1Name, CAST( 'a' AS varchar(50)) AS clumn2name
-- comment out the SP content when you want to import it.
< proper SP content >
END
Then drop the stored proc and create the original.
Save this temporary importing SP you have made in case you need it again, though.
EF doesn't support importing stored procedures which build result set from:
Dynamic queries
Temporary tables
Re-write your stored procedure to use a table variable instead.
remember to drop the stored procudure and function import from your model before updating as it wont generate the complex type unless it also adds the stored procedure. or go to function import properties and use the get column information feature after updating your stored procedure.
create procedure [dbo].[usp_InsertOrUpdate]
/*if your table(tbl_InsertOrUpdate) as 3 columns like uniqueid,col1,col2*/
#uniqueId bigint NULL,/*if insert send value as null or 0*/
#col1 bigint null,
#col2 [varchar](500) NULL
as
begin
set nocount ON
SET FMTONLY OFF
/* for giving result which column updated(uniqueId) and is it insert or update(IsInsert)*/
declare #varResult table (uniqueId bigint ,IsInsert bit )
/*create a var table before inserting original table*/
declare #varInsertOrUpdate table (
uniqueId bigint ,
col1 [bigint] ,
col2 [varchar]
)
/*default we are feel as update only*/
insert into #varResult (uniqueId,IsInsert) values (#uniqueId,0)
/*insert into var table*/
INSERT INTO #varInsertOrUpdate (uniqueId,col1,col2)
VALUES
(#uniqueId,#col1,#col2)
/*Insert into original table with where condition without if else*/
INSERT INTO tbl_InsertOrUpdate (col1,col2)
select col1,col2 from #varInsertOrUpdate
where uniqueId!=0;
/*if its insert updating result returning table*/
update #varResult set
uniqueId=IDENT_CURRENT('tbl_InsertOrUpdate'),
IsInsert=1 where #uniqueId=0;
/*updating table where #uniqueid is null or empty*/
UPDATE tbl_InsertOrUpdate
SET col1=#col1,
col2=#col2,
WHERE uniqueId=#uniqueId and #uniqueId!=0
select * from #varResult
end
To add complex type correctly, go Model browser, right click on function, then display edit, click edit an fill the dialog box. The name of the function should be same as name of the stored procedure. Click OK button. Now function is created. Then right click on the created function and go edit again.There is a update button aside of complex type button.Update it using that update button. Now complex type is created completely.
This is my SP to Implement the multiple search
***************************************************
CREATE PROCEDURE [dbo].[uspSEARCH_POSITIONS]
#OBJ_TYPE_REQUEST varchar(2000),--'FIRST_NAME;SEARCHVALUE|LAST_NAME;SEARCHVALUE|JOB_DESCRIPTION;SEARCHVALUE'
#DELIMITER varchar(10) --'| Which seperates the col;searchvalue|col;searchvalue
AS
BEGIN
SET FMTONLY OFF
DECLARE
#lLastName varchar(100),
#lFirstName varchar(100),
#lPositionNumber varchar(20),
#lJobDescription varchar(50),
#lJobCode varchar(20),
#lOccupancyIndicator varchar(50),
#ldeleimitercolsearchval varchar(10)
SET #ldeleimitercolsearchval =';'
CREATE TABLE #TempTable (ColSearchValues VARCHAR(2000))
INSERT INTO #TempTable
SELECT * FROM [dbo].[fnSplit](#OBJ_TYPE_REQUEST,#DELIMITER)--'fname;searchvalfname|lname;searchvallname|jobcode;searchvaljobcode','|')
SELECT #lLastName=SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%last%'
SELECT #lFirstName =SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%first%'
SELECT #lPositionNumber =SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%position%'
SELECT #lJobDescription=SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%jobd%'
SELECT #lJobCode=SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%jobc%'
SELECT #lOccupancyIndicator=SUBSTRING(ColSearchValues,CHARINDEX(#ldeleimitercolsearchval ,ColSearchValues)+1,LEN(ColSearchValues)) from #TempTable where lower(ColSearchValues) like '%ccupancy%'
SELECT [PS].[POSITION_NUMBER]
,[PS].[COST_CENTER]
,[PS].[JOB_CODE]
,[PS].[JOB_CODE_DESCRIPTION]
,[PS].[SITE_CODE]
,[EMP].[EMPLOYEE_ID]
,[EMP].[EIN]
,[EMP].[GRADE]
,[EMP].[LOGIN_ID]
,[EMP].[FIRST_NAME]
,[EMP].[LAST_NAME]
,LTRIM(RTRIM(ISNULL([EMP].[LAST_NAME],''))) + ',' +LTRIM(RTRIM(ISNULL([EMP].[FIRST_NAME],''))) AS [FULL_NAME]
,[EMP].[DISTRICT]
,[EMP].[SUPERVISOR_EIN]
,COUNT(*) OVER() AS TOTAL_RECORD_COUNT
FROM [DBSERVER].[dbo].[uvwPOSITION_SEARCH] PS
LEFT JOIN [DBSERVER].[dbo].[uvwEMPLOYEES] EMP
ON PS.POSITION_NUMBER=EMP.POSITION_NUMBER
WHERE
(#lLastName IS NULL OR [LAST_NAME] LIKE '%' + #lLastName + '%')
AND (#lFirstName IS NULL OR [FIRST_NAME] LIKE '%' + #lFirstName + '%')
AND (#lPositionNumber IS NULL OR [PS].[POSITION_NUMBER] LIKE '%' + #lPositionNumber + '%')
AND (#lJobDescription IS NULL OR [PS].[JOB_CODE_DESCRIPTION] LIKE '%' + #lJobDescription + '%')
AND (#lJobCode IS NULL OR [PS].[JOB_CODE] LIKE '%' + #lJobCode + '%')
AND (#lOccupancyIndicator IS NULL OR [EMP].[FILLED_VACANT] LIKE '%' + #lOccupancyIndicator + '%')
END
Now you can consume above SP in edmx using below
Adding stored procedures complex types in Entity Framework
Why can't Entity Framework see my Stored Procedure's column information?
And in case you have to update your SP below worked for me.
Updating Complex Type if Stored Procedure Updates
How Do I Get Entity Framework To Update Complex Types?
For me, Im having problems where importing my Stored Procedure into EF is not generating the Complex Entity return object (automatically). I found however, after commenting out sections of my sproc (aka stored procedure), that when I then re-imported the stored procedure back in (ie refreshed using the Get Column Information button in the Function Import Edit screen), that the Complex type could then be generated!
In short, there could be a where clause (or maybe something else) causing EF to not generate the Complex Type. Try commenting out sections of your sproc and re-importing the sproc to
UPDATE:
Further to my investigation above, I found that the reason the Complex Entity was not being generated was because my sproc was using a view (instead of a typical table). For curiosity sake, I changed the view to another table just to see what would happen, and the complex entity generated.
So, in short, it looks like Complex Entities might not generate automatically if you have a view. To try, I ripped out the view temporarily, re-import the sproc, generated the Complex Entity, then put the view back in. But now my code gives exceptions.
Will update on this later when I learn more =)
UPDATE:
Fixed the issue. Really silly mistake! The viewname that I was using was not spelled right =D. Im sort of angry that an error wasnt thrown by Sql Server when I created the sproc..... I guess that is life :) Alas, problem now fixed!
The issue of complex type not appearing may happen due to a different reason as well which is what I faced in our case. The issue was due to a syntax error in the SPROC where temp table was defined as below -
create table #temp(
col1 int,
col2 nvarchar(100),
col3 nvarchar(100), -- Notice the comma in the end
);
Surprisingly, SQL Server doesn't throw any error when you compile the sproc. Removing the comma fixed the problem for us.
In short, while some of the above solutions might work depending on the specific issue, my suggestion is to check your sproc for such syntactical errors that SQL might ignore but could be the underlying reason for this problem. Thanks.
Go to the Model Browser
If you need to modify existing function
Under the Function Imports >> Select the function to be modified >> Click Edit
You will need to update the function to refresh and you can see the columns need to be added