InvalidOperationException on EF - c#

I'm using WPF MVVM Pettern and
I Have a simple table on SQL Server 2012 which its ID(key) column is computed in an StoredProcedure, called PersonInsert: (This is simplified, but what is computed is more complex than this, anyway it's an int at last)
USE [Guard]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[PersonInsert]
#FName NVARCHAR(50) ,
#LName NVARCHAR(50) ,
#ID INT = NULL OUTPUT
AS
BEGIN
BEGIN TRY
SELECT #ID = ISNULL(MAX(ID), 0) + 1
FROM dbo.Person
INSERT INTO [dbo].[Person]
( [ID] ,
[FName] ,
[LName]
)
VALUES ( #ID ,
#FName ,
#LName
)
END TRY
BEGIN CATCH
DECLARE #ErrMsg NVARCHAR(MAX) ,
#ErrSvr INT ,
#ErrStt INT
SELECT #ErrMsg = ERROR_MESSAGE() + '|' + ERROR_PROCEDURE() + '|'
+ CAST(ERROR_LINE() AS NVARCHAR(5)) ,
#ErrSvr = ERROR_SEVERITY() ,
#ErrStt = ERROR_STATE()
RAISERROR (#ErrMsg, #ErrSvr, #ErrStt)
END CATCH
END
GO
In the .net side, I use EF 6.1 Code-First to handle data and mapped to SPs so I have OnModelCreating like this:
modelBuilder.Entity(Of Person)().MapToStoredProcedures(
Sub(x)
x.Insert(Function(e) e.HasName("[dbo].[PersonInsert]"))
x.Update(Function(e) e.HasName("[dbo].[PersonUpdate]"))
x.Delete(Function(e) e.HasName("[dbo].[PersonDelete]"))
End Sub)
And My Model is:
<Table("Person")> _
Partial Public Class Person
<DatabaseGenerated(DatabaseGeneratedOption.None), Key> _
Public Property ID As Integer
Public Property FName As String
Public Property LName as String
End Class
Now the strange thing is, when I try to Insert Data (Adding New Person) at first time, the db.SaveChanged() works great, but for second time it throws InvalidOperation exception with a message:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Saving or accepting changes failed because more than one entity of type 'Shoniz.Guard.WPFGuardApplication.Person' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
He is right! the data is comitted successfully! and I'm right too, because my ID(key) column is computed right and it's completely unique. Either I have used the DatabaseGeneratedAttribute with none value on ID, but the result is the same :(
Even when I re-query the db to check about duplicate keys, I find NOTHING!
Why this exception is thrown?
How can I prevent that?
Is there anyway to Ignore the changes after db.SaveChanges()?

First of all change DatabaseGeneratedOption.None to DatabaseGeneratedOption.Indetity
and finally change stored procedure to this:
BEGIN TRY
SELECT #ID = ISNULL(MAX(ID), 0) + 1
FROM dbo.Person
INSERT INTO [dbo].[Person]
( [ID] ,
[FName] ,
[LName]
)
VALUES ( #ID ,
#FName ,
#LName
)
--You have to tell EF this is returned Id of inserted person
SELECT #ID as ID
END TRY

Related

How should I handle the retrieval of a recently inserted record ID so that it may be inserted into another table?

I have a Vendor object within my View Model. When I insert this vendor record into the database, I want to retrieve the ID of this Vendor because it will be used as a foreign key on another table immediately after (in a different sproc). I'm attempting to do this with test as the ID I need to retrieve. The following code doesn't work because proc_amcInsertApplicationServerRelationship expects an integer, but test is of type of Object Parameter.
I guess my questions are:
Am I approaching this correctly? If not, what would be a better approach? Also, based on my current approach, is there something simple I'm overlooking that I could do to get this to work? Here's my code (sorry if I'm not providing enough detail):
[HttpPost]
public ActionResult Create(ApplicationViewModel applicationViewModel)
{
try
{
// TODO: Add insert logic here
ObjectParameter test = new ObjectParameter("ID", typeof (int));
var vendorID = db.proc_amcInsertNewVendor(applicationViewModel.Vendor.Company, applicationViewModel.Vendor.StreetAddress, applicationViewModel.Vendor.SecondaryStreetAddress,
applicationViewModel.Vendor.City, applicationViewModel.Vendor.State, applicationViewModel.Vendor.ZipCode, applicationViewModel.Vendor.PhoneNumber,
applicationViewModel.Vendor.Website, test);
foreach (var serverID in applicationViewModel.ServerIDs)
{
db.proc_amcInsertApplicationServerRelationship(test, serverID);
}
return RedirectToAction("Index");
}
catch (Exception exception)
{
return View();
}
}
EDIT: Per Request, here's my stored procedure.
#Company varchar(100)
,#StreetAddress varchar(100)
,#SecondaryStreetAddress varchar(50)
,#City varchar(50)
,#State varchar(50)
,#ZipCode varchar(10)
,#PhoneNumber varchar(15)
,#Website varchar(200)
,#ID int = NULL OUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO [dbo].[amc_Vendors]
(
[Company]
,[StreetAddress]
,[SecondaryStreetAddress]
,[City]
,[State]
,[ZipCode]
,[PhoneNumber]
,[Website]
)
VALUES
(
#Company
,#StreetAddress
,#SecondaryStreetAddress
,#City
,#State
,#ZipCode
,#PhoneNumber
,#Website
)
SET #Id = SCOPE_IDENTITY()
END
GO
In your SP after insert statement. Use
Select ##IDENTITY As ReturnedId
In your service code, do:
int Id=db.proc_amcInsertApplicationServerRelationship(serverId).FirstOrDefault().ReturnedId;
Use this Id for your further processing.
There is solution to this but you need to make sure for the followings:
-What you can try with your current code is return Identity of recently added Items with ##Identity in SQL procedure and get the same from command as return value or as output parameter.
Use the returned value and sent the same in next operation.
But as far as my expertise says this would be not good option. Since you are having dependencies for Query execution, You need to implement Transaction as well.
you can handle them at both database and Application level
Use Single SP to insert records in both table with Transaction or
Use Transaction class at Application level in C#
Hope these would be helpful
Do Following:
In Your SP:
CREATE PROCEDURE dbo.YourSPName
#Param1 varchar(156),
#Id Int OUTPUT
AS
SET NOCOUNT ON;
SELECT #Id = ##IDENTITY
In your C# code:
var outputParameter = new ObjectParameter("Id", typeof(int));
context.YourSPName("ParamValue", outputParameter);
Console.WriteLine(outputParameter.Value);

How to write this stored procedure

I have a table Student. This table has a column ID which is auto-increment. I want to write a stored procedure to add a new student and return the ID. I'm newbie at this.
This is my code. Please check if it's wrong and fix for me and show me how to code to use it in C#. I used Entity Framework 4.
#Name NVARCHAR(50),
#Birthday DATETIME,
#ID bigint OUTPUT
AS
BEGIN
SET XACT_ABORT ON
BEGIN
INSERT INTO [Student](Name, Birthday) VALUES (#Name, #Birthday);
SELECT #ID = SCOPE_IDENTITY()
END
END
It is better you can C# code instead SP when your working with EF4.
Using Entity Framework, this is all done automagically for you.
using (MyEntities context = new MyEntities())
{
var student = new Student()
{
Name = "some value",
Birthday = "some Birthday"
};
context.Students.AddObject(student);
context.SaveChanges();
int ID = student.ID; // You will get here the Auto-Incremented table ID value.
}
Saving in Entity Framework will automatically update the "auto-increment" ID column. You just read it out after the call to .SaveChanges();
EDIT:
Also read this post if you encounter any issues getting the "auto-increment" ID value.
#Name NVARCHAR(50),
#Birthday DATETIME,
#ID bigint OUTPUT
AS
BEGIN
SET XACT_ABORT ON
BEGIN
INSERT INTO [Student](Name,Birthday)
VALUES(#Name,#Birthday);
SELECT #ID = SCOPE_IDENTITY()
END
I just added commas in between fields
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.aspx
This should provide you all the information you need to build a C# application calling your Stored Procedure.

Issue with newid() after dropping table constraints using t-sql

There is a scenario where I have to drop primary key on a existing table and then insert a record into it. The table has a column called GUID as shown below
Create Table TEST_TABLE_VALUE (
TEST_TABLE_ID int Identity(1,1),
TEST_TABLE_VALUE int,
GUID uniqueidentifier Not Null Default newid(),
Primary Key (TEST_TABLE_ID, TEST_TABLE_VALUE)
)
Dropped the constraints using below code
Declare #TableName nvarchar(100)
Declare #TableId int
Declare #ConstraintName varchar(120)
Declare #IndexName varchar(120)
Declare #Command varchar(256)
Set #TableName = 'TEST_TABLE_VALUE'
Select #TableId = id From sysobjects Where [type]='U' and [name]=#TableName
Declare ConstraintDropCursor Cursor Local Fast_Forward
For Select name from sysobjects where (type='K' Or type='D' or type='F' or type='C') and parent_obj = #TableId
For Read Only
Open ConstraintDropCursor
Fetch Next From ConstraintDropCursor Into #ConstraintName
While ##Fetch_Status != -1
Begin
Set #Command = 'Alter Table dbo.' + #TableName + ' Drop Constraint ' + #ConstraintName
exec(#Command)
Fetch Next From ConstraintDropCursor Into #ConstraintName
End
Close ConstraintDropCursor
DeAllocate ConstraintDropCursor
After dropping the constraints when I tried to insert data into the table
Insert Into TEST_TABLE_VALUE (TEST_TABLE_VALUE) Values(1)
but got the below error:
Cannot insert the value NULL into column 'GUID', table 'CustApp1.dbo.TEST_TABLE_VALUE1'; column does not allow nulls. INSERT fails.
How can I solve this issue?
You have dropped the default for GUID column and it is not nullable column.Thus it is causing the issue. In case you want to insert lots of data and do not want constraint for maybe perf reasons. Then atleast do not drop the defaults for not nullable columns.
Well if you drop the default constraint you end up with
Create Table TEST_TABLE_VALUE (
TEST_TABLE_ID int Identity(1,1),
TEST_TABLE_VALUE int,
GUID uniqueidentifier Not Null,
Primary Key (TEST_TABLE_ID, TEST_TABLE_VALUE)
)
so if you wish to continue down this path you either have to provide a value for the GUID column
or also do
ALTER TABLE TEST_TABLE_VALUE ALTER COLUMN GUID uniqueidentifier NULL
to allow nulls.

How to default database value when system date change

I'm currently doing a banking website for my project, using C# and asp.net. One of the function is 'Remaining Daily Limit'. I need the database value to change back to default once I changed the system date to another day. Example : If a user had $500 (default value) as a daily limit, and he used all up. The next day, he will have $500 again. May I know how should I go about it?
Here is a SQL script that will create how I would setup your db with some sample data:
CREATE TABLE tblBankCustomer
(
BankCustomerId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, FirstName NVARCHAR(100) NOT NULL
, LastName NVARCHAR(100) NOT NULL
, DailySpendingLimit DECIMAL(38, 2) NOT NULL CONSTRAINT DF_tblBankCustomer_DailySpendingLimit DEFAULT(500)
)
CREATE TABLE tblTransactionType
(
TransactionTypeId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, TransactionType VARCHAR(50) NOT NULL
)
INSERT tblTransactionType (TransactionType)
VALUES ('Deposit')
, ('Withdrawal')
CREATE TABLE tblTransaction
(
TransactionId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, BankCustomerId INT NOT NULL
, TransactionDate DATE NOT NULL
, Amount DECIMAL(38, 2) NOT NULL
, TransactionTypeId INT NOT NULL
)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblBankCustomer
FOREIGN KEY (BankCustomerId)
REFERENCES tblBankCustomer(BankCustomerId)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblTransactionType
FOREIGN KEY (TransactionTypeId)
REFERENCES tblTransactionType(TransactionTypeId)
INSERT tblBankCustomer
(
FirstName
, LastName
)
VALUES ('Jeremy', 'Pridemore')
, ('K', 'YQ')
INSERT tblTransaction
(
BankCustomerId
, TransactionDate
, Amount
, TransactionTypeId
)
VALUES
(1, CURRENT_TIMESTAMP, 48.50, 2) -- Jeremy, Today, $48.50, Withdrawal
, (1, CURRENT_TIMESTAMP, 300.00, 2) -- Jeremy, Today, $300, Withdrawal
, (1, CURRENT_TIMESTAMP, -200.00, 1) -- Jeremy, Today, $200, Deposit
, (2, CURRENT_TIMESTAMP, 285.00, 2) -- K, Today, $285, Withdrawal
, (2, CURRENT_TIMESTAMP, 215.00, 2) -- K, Today, $215, Withdrawal
GO
CREATE FUNCTION fGetRemainingSpendingLimit
(
#BankCustomerId INT
, #Date DATE
)
RETURNS DECIMAL(38, 2)
BEGIN
SET #Date = ISNULL(#Date, CURRENT_TIMESTAMP)
DECLARE #RemainingLimit DECIMAL(38, 2) =
(SELECT
SUM([Transaction].Amount)
FROM tblBankCustomer Customer
INNER JOIN tblTransaction [Transaction]
ON [Transaction].BankCustomerId = Customer.BankCustomerId
AND [Transaction].TransactionDate = #Date
INNER JOIN tblTransactionType TransactionType
ON TransactionType.TransactionTypeId = [Transaction].TransactionTypeId
AND TransactionType.TransactionType = 'Withdrawal'
WHERE Customer.BankCustomerId = #BankCustomerId)
RETURN #RemainingLimit
END
GO
-- Some sample selects
SELECT dbo.fGetRemainingSpendingLimit(1, NULL)
SELECT dbo.fGetRemainingSpendingLimit(2, NULL)
Then in C# you should know the customer's ID for the customer you're working with. If you're using somethiing like ADO.NET you can call this function directly and use the value in code.
Default values are usually stay in some config file. In your specific case, I would say that you can
or have a table with default values
or in the tables where is possible to have restore functionality have default column with corresponding values.
after, can restore them using some stored procedure.
You can create SQL job and schedule it to run every midnight, within the job you can execute a stored procedure reseting values to $500.
In my opinion, based on that there may be other requirements in the future, I'd have a table that looks like:
UserID AppliedOn Limit
1 1/1/2012 500
1 2/1/2012 750
This give you a historic view of limits, that you can give to the user or data-mine. In the same way, that is how I would apply current daily limits.
UserID AppliedOn Withdrawn
1 1/10/2012 125
1 1/10/2012 225
Now on 1/1/2012 it would be easy to determine what the amount left in the limit is without any jobs or triggers. And again you'd have historic values that can be data-mined for other features.
SELECT
ul.Limit - uw.Sum as LimitLeft
FROM
UserLimit ul
INNER JOIN (
SELECT
UserID,
AppliedOn,
SUM(Limit) as Sum
FROM
UserLimit
Group by
UserID,
AppliedOn) uw on ul.UserID = uw.UserID
and ul.AppliedOn = uw.AppliedOn
WHERE
ul.UserID = #userID
AND ul.AppliedOn = #dateInQuestion
(my raw SQL skills might be a bit rusty due to Entity Framework here).

How to put FK into another table using Stored Procedure

I have one login form like :
Username: Ashish
Password: Pass
Contact no: 1234567890
State: Maharashtra
Having two tables :
create table DGRegion
(DG_Regionno Int Identity (1,1) NOT Null constraint pk1 Primary Key,
DG_Username varchar(50),
DG_Password varchar(50),
DG_Contactno int ,
DG_StateNo int References DGState(DG_stateno))
Create table DGState
(DG_stateno Int Identity (1,1) NOT Null constraint pk Primary Key ,
DG_State varchar (50))
Now how can I create one stored procedure P which will enter values into DGRegion and DGState tables?
After click on submit I'm using asp.net.
Problem is: I can enter values into DGRegion table directly but how can I enter reference of state into table DGState?
Try something like this:
CREATE PROCEDURE dbo.EnterLoginData(#Username VARCHAR(50), #Password VARCHAR(50),
#ContactNo INT, #StateName VARCHAR(50))
AS BEGIN
DECLARE #StateID INT
-- check if state already exists
IF EXISTS(SELECT * FROM dbo.DGState WHERE DG_State = #StateName)
-- if it exists - retrieve the DG_StateNo
SELECT #StateID = DG_StateNo
FROM dbo.DGState
WHERE DG_State = #StateName
ELSE BEGIN
-- if it doesn't exists - insert new row
INSERT INTO dbo.DG_State(DG_State) VALUES(#StateName);
-- get the newly inserted row's ID using SCOPE_IDENTITY()
SELECT #StateID = SCOPE_IDENTITY()
END
INSERT INTO
dbo.DGRegion(DG_Username, DG_Password, DG_ContactID, DG_StateNo)
VALUES(#Username, #Password, #ContactNo, #StateID)
END
In the sp:
check if the state is in the state table
if not, put it into the state table
get the id (DG_stateno) of the state table, put in variable
execute your insert

Categories

Resources