NOTE: This is based on my question here but this is subtly different. I have resolved the issue on the other question, this is specifically regarding child entities.
I have a data-structure such that:
SecurityPolicy 1<---* SecurityPolicyRule
Therefore, a SecurityPolicy can have 0, one or many SecurityPolicyRules.
I am using Julie Lerman's Entity Framework book to implement some degree of concurrency checking, TDD and POCO support.
I understand that each table should have a rowversion/timestamp field, which is marked as ConcurrencyMode==Fixed.
I have decided to implement the CUD in Stored Procedures. My UPDATE Sproc is as follows:
create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicy
#ID int,
#Name nvarchar(256),
#Comment nvarchar(max)=null,
#timestamp timestamp
AS
declare #nameExists nvarchar(256)
select #nameExists= [Name] from M2_Core_SecurityPolicy where [Name]=#Name and [ID]<>#id
if (not #nameExists is null)
begin
raiserror (N'Name is already in use: %s',
11,
1,
#Name)
end
else
begin
update M2_Core_SecurityPolicy
set [Name]=#Name,
[Comment]=#Comment
where id=#id and [timestamp]=#timestamp
IF ##ROWCOUNT>0
SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicy WHERE id=#id
end
go
create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicyRule
(
#id int,
#RoleName nvarchar(256),
#Rank int,
#CanReadExecute bit=null,
#CanWrite bit=null,
#CanDelete bit=null,
#CanExport bit=null,
#Timestamp timestamp
)
AS
declare #roleExists nvarchar(256)
declare #securityPolicyID int
select #roleExists= [RoleName] from vw_aspnet_Roles where [RoleName]=#RoleName
if (#roleExists is null)
begin
raiserror (N'Role is not defined: %s',
11,
1,
#roleName)
end
else
begin
select #securityPolicyID=[SecurityPolicyID] from M2_Core_SecurityPolicyRule where [id]=#id
-- move all other rules up in priority
IF (SELECT COUNT(*) FROM M2_Core_SecurityPolicyRule WHERE [ID]<>#ID AND [SecurityPolicyID]=#SecurityPolicyID AND [Rank]=#Rank) > 0
BEGIN
UPDATE M2_Core_SecurityPolicyRule
SET [Rank]=[Rank]+1
WHERE [Rank] >= #rank
AND [SecurityPolicyID]=#SecurityPolicyID
AND [ID]<>#ID
END
update M2_Core_SecurityPolicyRule
set [RoleName]=#RoleName,
[Rank]=#Rank,
[CanReadExecute]=#CanReadExecute,
[CanWrite]=#CanWrite,
[CanDelete]=#CanDelete,
[CanExport]=#CanExport
where id=#id and [timestamp]=#timestamp
IF ##ROWCOUNT>0
SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicyRule WHERE id=#id
end
RETURN
go
I am testing this using some code that:
Creates a Security Policy
Adds a created Security Policy Rule to the Security Policy
Adds the Security Policy
Saves the updates
Adds 1 to the Rank of the Security Policy Rule
Saves the updates
The test is below:
[TestMethod()]
public void AddWithSecurityPolicyRuleChangeRankTest()
{
ICoreContext coreContext = new CoreEntities(_coreDbConnectionString);
CoreUnitOfWork coreUnitOfWork = new CoreUnitOfWork(coreContext);
SecurityPolicyRepository target = new SecurityPolicyRepository(coreUnitOfWork);
int originalCount = coreContext.SecurityPolicies.Count();
string securityPolicyName = "addwithsecuritypolicyrulechangeruletest";
int originalRank = 1;
SecurityPolicy entity = new SecurityPolicy()
{
Comment = null,
Name = securityPolicyName,
SecurityPolicyRules = new FixUpCollection<SecurityPolicyRule>()
};
entity.SecurityPolicyRules.Add(
new SecurityPolicyRule()
{
CanDelete = null,
CanExport = null,
CanReadExecute = null,
CanWrite = null,
Rank = originalRank,
RoleName = "User"
});
target.Add(entity);
coreUnitOfWork.Save();
entity.SecurityPolicyRules[0].Rank=originalRank+1;
coreUnitOfWork.Save(); // <-- exception thrown here
SecurityPolicy savedSecurityPolicy = target.GetAll().Single(q => q.Name.Equals(securityPolicyName, StringComparison.CurrentCultureIgnoreCase));
Assert.AreEqual(originalRank+1,savedSecurityPolicy.SecurityPolicyRules[0].Rank);
}
However, when I run this, it throws an exception at the highlighted line. The exception is:
System.Data.OptimisticConcurrencyException
was unhandled by user code
Message=Store update, insert, or
delete statement affected an
unexpected number of rows (0).
Entities may have been modified or
deleted since entities were loaded.
Refresh ObjectStateManager entries.
Source=System.Data.Entity
StackTrace:
at System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64
rowsAffected, UpdateCommand source)
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager
stateManager, IEntityAdapter adapter)
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager
entityCache)
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions
options)
at System.Data.Objects.ObjectContext.SaveChanges()
at MIGTurbo2.Core.Data.CoreEntities.Save()
in
D:\dev\migturbo2.0\MIGTurbo2.Core\Data\Core.Context.cs:line
92
at MIGTurbo2.Repositories.CoreUnitOfWork.Save()
in
D:\dev\migturbo2.0\MIGTurbo2.Repositories\CoreUnitOfWork.cs:line
26
at MIGTurbo2.Core.Tests.IntegrationTests.SecurityPolicyRepositoryTest.AddWithSecurityPolicyRuleChangeRankTest()
in
D:\dev\migturbo2.0\MIGTurbo2.Core.Tests\IntegrationTests\SecurityPolicyRepositoryTest.cs:line
524 InnerException:
And sure enough, no data has changed. ie. The [Rank] is still 1 from the first update (therefore, the INSERT). However, running it through SQL Profiler and Ayende's EF Profiler, no calls to the database are even made to make the UPDATE. So the relevance of the timestamp/rowversion is surely ... irrelevant?
What could be causing this? I don't want to have to Refresh the DB on every Save! This ONLY happens when updating the child entity. Changing the parent SecurityPolicy works fine.
Update 1:
When running the INSERT code, the SecurityPolicyRule is created fine. This issue is specific to UPDATE and DELETE operations.
Update 2:
I've figured out that if I set the state of the entity, it does get submitted to the database. The EF is obvioulsy picking up my State property (using the mechanism explained in Julie's book) fine now. So my test code now looks like this when updating the item:
entity.SecurityPolicyRules[0].Rank=originalRank+1;;
entity.SecurityPolicyRules[0].State = IWW.Elements.State.Modified;
However, despite the call and parameters sent to the database being present and correct, it is still not working. Interestingly, as shown in the screenie below, the last line is displayed AFTER the exception. Though I'm sure that this is only down to the Entity Framework Profile "flushing" its monitors.
Is the Stored Procedure of the child entity returning correct data?
Related
I'm trying to detect and silently discard any duplicate INSERTs into a table, and if a dupe detected, return the ID (Primary Key) of the existing record. Otherwise, insert the record and return the new ID.
I can do this with either a RULE or a TRIGGER, however both have drawbacks. Here's an example of my RULE:
CREATE OR REPLACE RULE territory_products_ignore_duplicate_inserts AS
ON INSERT TO territory_products
WHERE (EXISTS ( SELECT 1
FROM territory_products tp
WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id)) DO INSTEAD SELECT tp.id
FROM territory_products tp WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id LIMIT 1;
Testing this in a SQL console or psql with an INSERT works fine. If there's a dupe, it'll return the first existing record's id and not do the INSERT. Otherwise, it will go ahead with the INSERT. However, in Rails, it fails and returns this error:
ERROR: cannot perform INSERT RETURNING on relation "territory_products"
HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
Moving to a TRIGGER, I try this:
CREATE OR REPLACE Function territory_products_ignore_dups() Returns Trigger
As $$
Begin
If Exists (
Select id From territory_products tp
Where tp.territory_id = NEW.territory_id And tp.product_id = NEW.product_id
) Then
Return NULL;
End If;
Return NEW;
End;
$$ Language plpgsql;
Create Trigger territory_products_ignore_dups
Before Insert On territory_products
For Each Row
Execute Procedure territory_products_ignore_dups();
This also works fine, except that I can't get it to return the existing ID, because of the Return NULL (which is required to disallow the INSERT).
Can anyone resolve either of these issues, so I get the result I'm looking for? (e.g. silently discard the INSERT in case of a dupe and return the ID of the existing record. Or if the INSERT is successful, return the ID of the new record).
Add a unique index to your database:
# migration file
add_index : territory_products, [:territory_id, :product_id], unique: true
In your logic look for the existing record or create if doesn't exist. It will raise an exception in case of a race condition. Just search again for the record.
#logic when finding/creating record
begin
TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first_or_create!
rescue ActiveRecord::RecordNotUnique
# In case it already exists
TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first
end
I have created a mvc4 application with entity framework. Added a entity model in project. Now i have
added a store procedure in model browser and editing import function. There is a option Returns a collection of which contains none,scalers,complex,entities. I am not able to decide which one to choose as my store procedure returns multiple output parameters. If it returns single parameter then i can choose scalers, if table then entities. But it returns more then one output parameter so which one to choose. I am attaching store procedure screen shot.
Your stored procedure uses reference parameters, but doesn't actually return anything. To make a stored procedure return something, end the procedure with a SELECT statement that doesn't set a variable.
So, your code with look something like this:
CREATE PROC [wickedbrains].[uspValidateAdminLogin]
#UserName VARCHAR(50),
#Password VARCHAR(50)
AS
BEGIN
DECLARE #UserId INT = NULL,
#Res INT = 0;
IF EXISTS(SELECT '' FROM tblAdminUser WHERE UserName = #UserName AND Pwd = #Password)
BEGIN
SELECT #UserId = Id FROM tblAdminUser WHERE UserName = #UserName AND Pwd = #Password;
SET #Res = 1;
END
SELECT #UserId, #Res;
END
Once you've fixed your stored procedure, as Ehsan described, you can fix your imported stored procedure after the fact by clicking Get Column Information, then clicking Create New Complex Type.
If you absolutely have to use output parameters, you will have to retrieve the parameters with code as you would with reference parameters used in any other function. The point is that stored procedures that only use output parameters don't have a return type. See this answer for further details: https://stackoverflow.com/a/6193419/12116036
I tried implementing a call to Stored proc and the proc returns ID which will used later.
Everytime I execute I get the out parameter as -1. Below is my sample code:
OleDbCommand sqlStrProc = new OleDbCommand();
sqlStrProc.Connection = dbConn;
sqlStrProc.CommandText = "dbo.insert_test";
sqlStrProc.CommandType = CommandType.StoredProcedure;
sqlStrProc.Parameters.Add("#p_TestID", OleDbType.Integer, 255).Direction = ParameterDirection.Output;
sqlStrProc.Parameters.Add("#p_TestName", OleDbType.VarChar).Value = "Test";
sqlStrProc.Parameters.Add("#p_CreatedBy", OleDbType.VarChar).Value = "Test";
int personID = sqlStrProc.ExecuteNonQuery();
Row.outPersonID = personID;
personID is always -1. What am I doing wrong here. Please help..!!
Below is the stored proc code
CREATE PROCEDURE [dbo].[INSERT_TEST]
#p_TestID int OUTPUT,
#p_TestName varchar (50),
#p_CreatedBy varchar (100)
AS
SET NOCOUNT ON
INSERT INTO Test(
TestName,
CreatedBy)
VALUES
( #p_TestName,
#p_CreatedBy)
SELECT #p_TestID = SCOPE_IDENTITY()
-1 could mean that the stored procedure failed to execute as desired and the transaction was rolled back. You may want to look for any truncation issues since you have different sizes for the 2 input parameters but are using the same input. Also I assume you have proper code to open and close connections etc?
-1 returned value is an error produced during the execution of your SP, this is due to the following reasons:
SP Structure: everytime you are executing the SP it tries to create it again while it already exists. so you have to either make it an ALTER PROCEDURE instead of CREATE PROCEDURE or do the following:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[INSERT_TEST]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[INSERT_TEST]
GO
CREATE PROCEDURE [dbo].[INSERT_TEST]
#p_TestID int OUTPUT,
#p_TestName varchar (50),
#p_CreatedBy varchar (100)
AS
Database Connection (Table Name and Location): you have to specify withe the OLEDB the ConnectionString that connects you to the write DB. try to test the full Table path; like the following;
INSERT INTO [DATABASENAME].[SHCEMA].[TABELNAME](
Name,
CreatedBy)
VALUES
( #p_TestName,
#p_CreatedBy)
Define your SP as :
CREATE PROCEDURE [NAME]
AS
BEGIN
END
thought it is not a problem, but it is a proper way to write your SPs in terms of connection transactions,
Let me know if it works fine with you :)
Regrads,
S.ANDOURA
I simplified the code a little while trying to debug:
[HttpPost]
public ActionResult Register(User model)
{
DateTime bla = new DateTime(2012, 12, 12);
try
{
User user = new User
{
gid = 1,
cid = 1,
firstName = model.firstName,
lastName = model.lastName,
email = model.email,
username = model.username,
password = model.password,
creationDate = bla,
active = 1
};
myContext.Users.AddObject(user);
myContext.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
return View();
}
The values are transmited accordingly. Users table:
[id] [int] IDENTITY(1,1) NOT NULL,
[cid] [int] NULL,
[gid] [int] NULL,
[firstName] [nvarchar](100) NOT NULL,
[lastName] [nvarchar](100) NOT NULL,
[email] [nvarchar](max) NOT NULL,
[username] [nvarchar](100) NOT NULL,
[password] [nvarchar](100) NOT NULL,
[creationDate] [datetime] NOT NULL,
[active] [int] NOT NULL,
CONSTRAINT [PK_Users_3213E83F0AD2A005] PRIMARY KEY CLUSTERED
I deleted all the foreign keys to be sure that nothing affects it. I am qute certain that at a previous moment it was working, but now I can not figure where the issue is.
It crashes while performing the savechanges:
{"An error occurred while updating the entries. See the inner exception for details."}
{"The member with identity '' does not exist in the metadata collection.\r\nParameter name: identity"}
I had the same error being thrown when I try to insert using EF, the error was
The member with identity 'Id' does not exist in the metadata collection.\r\nParameter name: identity
It wasn't obvious at first but the exception message was very concise because my database knows the column Id int but the property created for the object on my code was int ID so coming back to named mapping, Id is not mapped to ID.
So when an object with property ID is sent to database that only know Id you will get the above error.
I hope this helps, thanks
The issue was reproducing because of a trigger that was on the users table. Removed it and the issue is not reproducing anymore.
There is probably a trigger on the table being updated and it returns output. The output is thrown away but it conflicts with EF. Such output is often used to debug triggers (and forgotten to delete later):
select 'trigger called, i am here'
or there can be missing variable:
select column
instead of
select #variable=column
I think that the best solution is in this post. I used the 3rd option and works.
Here I report the reply in the link:
The issue could be related to a "instead of insert" trigger on one of
your tables.
The EF framework is performing validation on the inserted row of data
by calling scope_identity(). However, an "instead of insert" trigger
will change the context of the insertion in such a way that the EF
system's call to scope_identity will return null.
A couple ways around this:
Use a stored procedure to insert the data ( not tested )
Remove the instead of insert trigger ( triggers can cause other problems, so some people argue not to use them) ( tested, works!)
Turn off validation in the EF framework, so: context.Configuration.ValidateOnSaveEnabled = false ( tested, works!)
I had this same error today and spent a few frustrating hours trying to figure it out.
I was using Entity Framework to insert a record with an identity column into a SQL server database table. Simple enough.
The table had a trigger on it which in turn ran a stored procedure. The stored procedure had a line in it:
select newid()
This is the line that broke Entity Framework.
For tables with identity columns, Entity Framework expects to be returned a single row with a single field that is the identity column.
it's because of trigger pass back value to EF
if you are using trigger. in my problem i must check a value by selecting from other table and using of 'select' cause error in EF, so you must replace 'select' with 'set'.
you can not use this code.
select #any= any from tablename
you should use set instead of select
set #any= (select any from tablename)
Somedays, I hate M$.
The member with identity 'ChangeID' does not exist in the metadata collection.
Parameter name: identity
I've spent two days trying to get around this.
I'm using MVC.
To get all the data I need in one fell swoop, I created a view of the table in the DB for this application, and tables in other databases. (You may update views, with some constraints.)
I do a get, and all my data is present in the record, keys, descriptions, foreign keys, etc.
I created triggers on my view, to update the portion of the view that came from the local table.
Instead of Delete worked fine.
Instead of Update worked fine.
This error kept raising it's head on Instead of Insert. I could NOT get the insert trigger to successfully insert into my table. If I ran an insert on the view, with all fields provided in the SQL Management Studio, it worked fine. I know the exact values being passed because I ran SQL Server Profiler to see the code being passed.
But when the app attempted the update, it failed with The member with identity 'ChangeID' does not exist in the metadata collection.
Clue up above, someone said, "MVC expects table key to be ID"
I renamed ChangeID as ID in my view, changed it in the App, and BAM! Now it works.
What did NOT work:
db.Configuration.ValidateOnSaveEnabled = false;
adding a select to the trigger to get scope identity
Why do I have to modify my DB or my view to satisfy some hidden M$ assumption?
None the less, after two very frustrating days, code is now working. Maybe this will save someone else some time as well.
Try this
[HttpPost]
public ActionResult Register(User model)
{
DateTime bla = new DateTime(2012, 12, 12);
try
{
model.gid = 1;
model.cid = 1;
model.creationDate = bla;
model.active = 1;
myContext.Users.AddObject(model);
myContext.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
return View();
}
I was having this issue and my fix, was that in my connection-string metadata I did not specify my model in the .msl format.
See more info here
In my case, SetName of the entity was incorrect. Shortly, this worked for me:
Use
myContext.AddObject(nameOfSetEntity, user);
Instead of:
myContext.Users.AddObject(user);
To get the whole correct name of the entity (thanks to Nix's answer)
string className = typeof(User).Name;
var container = myContext.MetadataWorkspace.GetEntityContainer(myContext.DefaultContainerName, System.Data.Metadata.Edm.DataSpace.CSpace);
string nameOfSetEntity= (from meta in container.BaseEntitySets
where meta.ElementType.Name == className
select meta.Name).First();
Context.AddObject(nameOfSetEntity, user);
I have a data-structure such that:
SecurityPolicy 1<---* SecurityPolicyRule
Therefore, a SecurityPolicy can have 0, one or many SecurityPolicyRules.
I am using Julie Lerman's Entity Framework book to implement some degree of concurrency checking, TDD and POCO support.
I understand that each table should have a rowversion/timestamp field, which is marked as ConcurrencyMode==Fixed.
I have decided to implement the CUD in Stored Procedures. My UPDATE Sproc is as follows:
create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicy
#ID int,
#Name nvarchar(256),
#Comment nvarchar(max)=null,
#timestamp timestamp
AS
declare #nameExists nvarchar(256)
select #nameExists= [Name] from M2_Core_SecurityPolicy where [Name]=#Name and [ID]<>#id
if (not #nameExists is null)
begin
raiserror (N'Name is already in use: %s',
11,
1,
#Name)
end
else
begin
update M2_Core_SecurityPolicy
set [Name]=#Name,
[Comment]=#Comment
where id=#id and [timestamp]=#timestamp
IF ##ROWCOUNT>0
SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicy WHERE id=#id
end
go
create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicyRule
(
#id int,
#RoleName nvarchar(256),
#Rank int,
#CanReadExecute bit=null,
#CanWrite bit=null,
#CanDelete bit=null,
#CanExport bit=null,
#Timestamp timestamp
)
AS
declare #roleExists nvarchar(256)
declare #securityPolicyID int
select #roleExists= [RoleName] from vw_aspnet_Roles where [RoleName]=#RoleName
if (#roleExists is null)
begin
raiserror (N'Role is not defined: %s',
11,
1,
#roleName)
end
else
begin
select #securityPolicyID=[SecurityPolicyID] from M2_Core_SecurityPolicyRule where [id]=#id
-- move all other rules up in priority
IF (SELECT COUNT(*) FROM M2_Core_SecurityPolicyRule WHERE [ID]<>#ID AND [SecurityPolicyID]=#SecurityPolicyID AND [Rank]=#Rank) > 0
BEGIN
UPDATE M2_Core_SecurityPolicyRule
SET [Rank]=[Rank]+1
WHERE [Rank] >= #rank
AND [SecurityPolicyID]=#SecurityPolicyID
AND [ID]<>#ID
END
update M2_Core_SecurityPolicyRule
set [RoleName]=#RoleName,
[Rank]=#Rank,
[CanReadExecute]=#CanReadExecute,
[CanWrite]=#CanWrite,
[CanDelete]=#CanDelete,
[CanExport]=#CanExport
where id=#id and [timestamp]=#timestamp
IF ##ROWCOUNT>0
SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicyRule WHERE id=#id
end
RETURN
go
I am testing this using some code that:
Creates a Security Policy
Adds a created Security Policy Rule to the Security Policy
Adds the Security Policy
Saves the updates
Adds 1 to the Rank of the Security Policy Rule
Saves the updates
The test is below:
[TestMethod()]
public void AddWithSecurityPolicyRuleChangeRankTest()
{
ICoreContext coreContext = new CoreEntities(_coreDbConnectionString);
CoreUnitOfWork coreUnitOfWork = new CoreUnitOfWork(coreContext);
SecurityPolicyRepository target = new SecurityPolicyRepository(coreUnitOfWork);
int originalCount = coreContext.SecurityPolicies.Count();
string securityPolicyName = "addwithsecuritypolicyrulechangeruletest";
int originalRank = 1;
SecurityPolicy entity = new SecurityPolicy()
{
Comment = null,
Name = securityPolicyName,
SecurityPolicyRules = new FixUpCollection<SecurityPolicyRule>()
};
entity.SecurityPolicyRules.Add(
new SecurityPolicyRule()
{
CanDelete = null,
CanExport = null,
CanReadExecute = null,
CanWrite = null,
Rank = originalRank,
RoleName = "User"
});
target.Add(entity);
coreUnitOfWork.Save();
entity.SecurityPolicyRules[0].Rank=originalRank+1;
coreUnitOfWork.Save(); // <-- exception thrown here
SecurityPolicy savedSecurityPolicy = target.GetAll().Single(q => q.Name.Equals(securityPolicyName, StringComparison.CurrentCultureIgnoreCase));
Assert.AreEqual(originalRank+1,savedSecurityPolicy.SecurityPolicyRules[0].Rank);
}
However, when I run this, it throws an exception at the highlighted line. The exception is:
System.Data.OptimisticConcurrencyException
was unhandled by user code
Message=Store update, insert, or
delete statement affected an
unexpected number of rows (0).
Entities may have been modified or
deleted since entities were loaded.
Refresh ObjectStateManager entries.
Source=System.Data.Entity
StackTrace:
at System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64
rowsAffected, UpdateCommand source)
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager
stateManager, IEntityAdapter adapter)
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager
entityCache)
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions
options)
at System.Data.Objects.ObjectContext.SaveChanges()
at MIGTurbo2.Core.Data.CoreEntities.Save()
in
D:\dev\migturbo2.0\MIGTurbo2.Core\Data\Core.Context.cs:line
92
at MIGTurbo2.Repositories.CoreUnitOfWork.Save()
in
D:\dev\migturbo2.0\MIGTurbo2.Repositories\CoreUnitOfWork.cs:line
26
at MIGTurbo2.Core.Tests.IntegrationTests.SecurityPolicyRepositoryTest.AddWithSecurityPolicyRuleChangeRankTest()
in
D:\dev\migturbo2.0\MIGTurbo2.Core.Tests\IntegrationTests\SecurityPolicyRepositoryTest.cs:line
524 InnerException:
And sure enough, no data has changed. ie. The [Rank] is still 1 from the first update (therefore, the INSERT). However, running it through SQL Profiler and Ayende's EF Profiler, no calls to the database are even made to make the UPDATE. So the relevance of the timestamp/rowversion is surely ... irrelevant?
What could be causing this? I don't want to have to Refresh the DB on every Save!
Update 1
Having run the SQL that should execute:
declare #t timestamp
select #t=[timestamp] from M2_Core_SecurityPolicyRule where ID=1
exec [sp_M2_Core_UpdateSecurityPolicyRule] #id=1, #roleName='User',#Rank=2,#Timestamp=#t
It works fine. There is something inside EF occurring that is blocking the call
Update 2
By breaking through the code, I find that the following occurs:
The item is created (obviously, Timestamp is null)
The item is added (Timestamp still null)
The changes are saved (this issues the INSERT)
The [Timestamp] field is then NOT UPDATED from the DB
Therefore, the subsequent UPDATE fails, as [Timestamp] IS NULL
So why would the [Timestamp] field not be updated?
Generally speaking, this is because the timestamp of the entity in the objectstatemanager no longer matches what is in the DB.
Call
coreContext.Refresh(RefreshOptions.StoreWins (or .ClientWins depending on what you want),entity);
to sync the entity and the DB before calling the save.
For a good post explaining optimistic concurrency see
http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/457f2196-dd21-4188-8185-2561b954c54b
or
http://msdn.microsoft.com/en-us/library/bb738618.aspx
It seems that there I might have misunderstood Julie Lerman's book or there is a slight change required to how she implements her Stored Procedures.
I have changed the Model and Stored Procedures such that the SProcs return the Timestamp and the Model picks it up. This therefore means that the [Timestamp] field will not be null.
So the INSERT SProc now looks like:
create PROCEDURE dbo.sp_M2_Core_InsertSecurityPolicy
#Name nvarchar(256),
#Comment nvarchar(max)=null
AS
declare #nameExists nvarchar(256)
declare #id int
select #nameExists= [Name] from M2_Core_SecurityPolicy where [Name]=#Name
if (not #nameExists is null)
begin
raiserror (N'Name is already in use: %s',
11,
1,
#Name)
end
else
begin
INSERT INTO M2_Core_SecurityPolicy
([Name],Comment)
values
(#Name,#Comment)
IF ##ROWCOUNT > 0
BEGIN
SET #id=SCOPE_IDENTITY()
SELECT #id as ID,[Timestamp] FROM M2_Core_SecurityPolicy WHERE ID=#id
END
end
go
and the Mapping is changed so that it picks up the "new" field: