Windows Azure SQL Database - Identity Auto increment column skips values - sql-server

Currently working on an ASP.Net MVC 4 application using Entity Framework 5. Used CodeFirst for initial development phase. But have now disabled the Automatic Migrations and designing new tables directly using SSMS and writing POCO. Everything is working good.
Recently, identified a weird issue in Production. The records in one of the initially designed tables skipped auto-increment identity value by more than 900 numbers. This has happened 3 times within last 3 months. Debugged the application locally but could not reproduce. There isn't any pattern or trend observed.
Model:
public class Enquiry
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int64 EnquiryId { get; set; }
[Required]
public int UserId { get; set; }
[Required]
public byte Bid { get; set; }
...
[Required]
public DateTime Created { get; set; }
[Required]
public DateTime Modified { get; set; }
}
public class EnquiryDetail
{
[Key]
public Int64 EnquiryId { get; set; }
[Required]
public int CreditScore { get; set; }
[Required]
public byte BidMode { get; set; }
public virtual Enquiry Enquiry { get; set; }
}
DBContext:
public class EscrowDb : DbContext
{
public EscrowDb()
: base("name=DefaultConnection")
{
}
public DbSet<Enquiry> Enquiries { get; set; }
public DbSet<EnquiryDetail> EnquiryDetails { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<EnquiryDetail>()
.HasRequired<Enquiry>(ed => ed.Enquiry)
.WithRequiredDependent(e => e.EnquiryDetail);
}
}
Controller:
[Authorize]
public class EnquiryController : Controller
{
private EscrowDb _db = new EscrowDb();
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(EnquiryViewModel core)
{
var enquiry = new Enquiry();
// Some code to set properties using passed ViewModel
...
var enquiryDetail = new EnquiryDetail();
// Some code to set properties using passed ViewModel
...
enquiry.EnquiryDetail = enquiryDetail;
_db.Enquiries.Add(enquiry);
_db.SaveChanges();
}
}
All this code has been working fine so far except the identity value getting skipped sporadically by large gaps of almost 1000 numbers.
Has anybody come across such kind of issue? Please share your thoughts.

You may be out of luck here if you need to eliminate these gaps.
I hit this issue myself as I am developing/testing a new application. I'm intuiting what's happening here in sql azure based on what I've read about sql server 2012. I have not been able to find any documentation about this for sql azure.
From what I've read this is a feature that comes across as a bug IMO. In Sql server 2012 Microsoft added the ability to create sequences. Sequences record what values have been used in blocks of 1000. So lets say your sequence was progressing... 1, 2, 3, 4, 5... and then your sql server restarts. Well the sequence has already saved the fact that the block 1-1000 have already been used so it jumps you to the next 1000.... so your next value are 1001, 1002, 1003, 1004.... This improves performance of inserts when using sequences, but can result in unusual gaps. There is a solution to this for your sequence. When specifying you sequence add the "NOCACHE" parameter so that it doesn't save blocks of 1000 at a time. See here for more documentation.
Where this becomes an issue is that the Identity columns seem to have been changed to use this same paradigm. So when your server, or in this case your sql azure instance restarts you can get large gaps (1000's) in your identity columns because it is caching large blocks as "used". There is a solution to this for sql server 2012. You can specify a startup flag t272 to revert your identity to using the old sql server 2008 r2 paradigm. The problem is that I'm unaware (it may not be possible) of how to specify this in sql Azure. Can't find documentation. See this thread for more details on sql server 2012.
Check the documentation of identity here in the msdn. Specifically the section "Consecutive values after server restart or other failures". Here is what it says:
Consecutive values after server restart or other failures –SQL Server
might cache identity values for performance reasons and some of the
assigned values can be lost during a database failure or server
restart. This can result in gaps in the identity value upon insert. If
gaps are not acceptable then the application should use a sequence
generator with the NOCACHE option or use their own mechanism to
generate key values.
So if you need to have consecutive values you could try specifying a sequence with nocache instead of relying on your identity column. Haven't tried this myself, but sounds like you'll have trouble getting this to work with entity framework.
Sorry if this doesn't help much, but at least it's some info on what your experiencing.

Try the reseeding with trigger approach. I believe this should solve it example of its use and see more walkarounds at that link.
USE [TEST]
CREATE TABLE TEST(ID INT IDENTITY(1,1),VAL VARCHAR(10))
CREATE TRIGGER TGR_TEST_IDENTITY ON TEST
FOR INSERT
AS
DECLARE #RESEEDVAL INT
SELECT #RESEEDVAL = MAX(ID) FROM TEST
DBCC CHECKIDENT('TEST', RESEED, #RESEEDVAL)
INSERT INTO TEST(VAL)VALUES('a')
SELECT * FROM TEST
Since 'DBCC CHECKIDENT' is not supported in Azure now you can use the approach in this link
In that link i got some work arounds
Use GUID as key when using auto key of your SqlAzure
If integer key like my case let the record insert and go back and delete it and re insert it with the right key by Turing off identity with
set identity_insert XXXTable on -- this basically turns off IDENTITY
and then turning back on identity again when i am through with the insertion with the right key using
set identity_insert XXXTable off --this basically turns on IDENTITY
Note: this is not a good solution for a table that is receiving a massive insert request but might be useful for someone looking for a temporary way out

It seems there is no TF 272 work around for SQL Azure. I just noticed the issue in 2 tables (gaps of 999 and 1000) and thought it was a security breach before inspecting the two tables and checking inserted records.
See the last item of this MS TechNet discussion for details. Kind of re-assuring, but looks more like a bug than a feature.

I had this problem too and until this time i can not find any way, it seems entity has a bug or something like this.
i search on internet but fount nothing

This can happen easy. Beside server issues, this can be normal in application logic. Do not expect to make too much logic for identity values. But make your own numbers if you need them to mean something.
A very common reason I see this, is for rolled back transactions, which is good. I See the sample to reproduce in SQL server. This would, expectedly, affect anything such as MVC or entity framework using the database:
DROP TABLE IF EXISTS #PKIDTest
CREATE TABLE #PKIDTest (PKID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, Msg VARCHAR(100))
BEGIN TRANSACTION
INSERT INTO #PKIDTest (Msg) VALUES ('The'),('quick'),('brown fox'),('jumped'),('over')
COMMIT
SELECT * FROM #PKIDTest --PKID: 1-5
BEGIN TRANSACTION
INSERT INTO #PKIDTest (Msg) VALUES ('the'),('alzy'),('dog')
--PKID 6-8
ROLLBACK --"spelling error"
SELECT * FROM #PKIDTest --PKID: 1-5 (6-8 rolled back)
BEGIN TRANSACTION
INSERT INTO #PKIDTest (Msg) VALUES ('the'),('lazy'),('dog')
COMMIT
SELECT * FROM #PKIDTest --PKID: 1-5 , 9-11 (6-8 rolled back)
If you are seeing this behavior and do not expect to have rolled back transactions or removed rows, consider checking your error handling code to see if you are logging the errors. It is good to have a way to log when unexpected errors occur.
If you have a logging solution in place, consider trying to correlate the timeframes that the "skipped" values occur, and events in your log.

Related

EF Core Stored Procedure FromSqlRaw Does Not Give Updated Values

When running a procedure on EF Core 3 using FromSqlRaw that updates values in the table, EF DOES NOT return the updated values when I query the database for those changed values.
I have been able to reproduce this behavior. To reproduce create a new console app c# with .net core 3.1.
Copy paste the code below into your main Program.cs file:
using System;
using System.Linq;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace EfCoreTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// testing proc
var dbContext = new TestContext();
var tables = dbContext.TestTables.ToList();
var updated = dbContext.TestTables
.FromSqlRaw("execute testProc #Id=#Id, #Comments=#Comments", new object[]
{
new SqlParameter("Id", 1),
new SqlParameter("Comments", "testing comments 2"),
})
.ToList();
var again = dbContext.TestTables.ToList();
}
}
public class TestTable
{
public int TestTableId { get; set; }
public string Comment { get; set; }
}
public class TestContext : DbContext
{
public DbSet<TestTable> TestTables { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost\SQLEXPRESS;Database=TestDb;Trusted_Connection=True");
}
}
}
Ensure that the following packages are installed:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.SqlServer.Design
Change your connection string if necessary.
Run dotnet ef migrations add initial
Run dotnet ef database update
Run the following code in your db:
drop procedure if exists testProc
go
create procedure testProc
#Id int,
#Comments nvarchar(max)
as
begin
update dbo.TestTables
set Comment = #Comments
where TestTableId = #Id;
select * from dbo.TestTables;
end
go
INSERT INTO [dbo].[TestTables]
(Comment) VALUES ('Test Comment');
So when you run the Main program on debug and put a breaker, you'll notice that NONE of the objects return values that were updated by the procedure when go to inspect it. While in debug if you run a select statement on the table you will see that the "Comment" field is indeed updated.
Why is this?
This is not specific to FromSql, but the way EF Core (all versions) tracking queries work.
Here is an excerpt from EF Core How Queries Work documentation topic:
The following is a high level overview of the process each query goes through.
The LINQ query is processed by Entity Framework Core to build a representation that is ready to be processed by the database provider
The result is cached so that this processing does not need to be done every time the query is executed
The result is passed to the database provider
The database provider identifies which parts of the query can be evaluated in the database
These parts of the query are translated to database specific query language (for example, SQL for a relational database)
One or more queries are sent to the database and the result set returned (results are values from the database, not entity instances)
For each item in the result set
If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
If so, the existing entity is returned
If not, a new entity is created, change tracking is setup, and the new entity is returned
Note the last bullet. What they do is basically an implementation of the so called client wins strategy (as opposed to database wins which you are looking for), and currently there is no way of changing that other than using no-tracking query.
In your example, insert AsNotTracking() somewhere in the queries (before ToList, after dbContext.TestTables - it really doesn't matter because it applies to the whole query), or just
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
and now you'll see the updated values (from your SP call or from other sessions to the same database).

On record creation Add Prefix with Id to a new column in SQL table using EF / EF Core 2

I have the following model
public class Student
{
public int Id { get; set; }
private string reference;
public string Reference
{
get
{
return this.reference;
}
set
{
this.reference = "ST" + Id;
}
}
}
and i am trying to write to the database Reference Column like
Id - Reference
1 - ST1
2 - ST2
My context class has the following configuration
public class SchoolContext : DbContext
{
public DbSet<Student> Student { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.Property(b => b.Id)
.ValueGeneratedOnAdd();
modelBuilder.Entity<Student>()
.Property(b => b.Reference)
.HasComputedColumnSql("[Id]")
.ValueGeneratedOnAdd();
}
}
But i am not getting what i am trying to. Please bear in mind i dont want to generate that in sql server
Ok, let's see :
[Id] is the identity and auto-increment
You need to fill the column [Reference] with "ST" + [Id], always
You don't want to perform anything in SQL server's side
You are using the EF Core
The facts :
HasComputedColumnSql and ValueGeneratedOnAdd has nothing to do with the way you fill the column [Reference]
Column [Id] is non-deterministic until a row is actually written in the table
Column [Reference] is not Computed Column type, because it actually has real data written in it by the application, even though it always be "ST" + [Id]
Solution with the EF :
Add the row into the entity
Save the changes
modify the column [Reference] of the same row by using a direct assignment like this :
student.Reference = "ST" + student.Id.ToString();
And again save the changes
Alternatively, you may consider to :
Make the [Reference] column a Computed Column in SQL Server's side
Or make the [Reference] property only exist in your class definition above, just without the setter. No need to keep it in the actual table, in my humble opinion.
Sorry, it may not solve your problem perfectly, because of your restrictions and limitations.
So, good luck!

How to use IRepository in Orchard with int key (NOT IDENTITY)

I'm using Orchard IRepository to manage data and save to db... that's what I'm trying to do.
My class is:
public class SocietaRecord
{
public virtual int Id { get; set; }
public virtual string Club { get; set; }
public virtual string Email { get; set; }
}
My Migration:
SchemaBuilder.CreateTable("SocietaRecord", table => table
.Column<int>("Id", column => column.PrimaryKey())
.Column<String>("Club", c => c.Nullable())
.Column<String>("Email", c => c.Nullable())
);
Please note that Id is NOT and IDENTITY
now when I call Repository Create the follow insert is performed:
(grabbed with sql profiler)
exec sp_executesql N'INSERT INTO Match_SocietaRecord
(Club, Email) VALUES (#p0, #p1);
select SCOPE_IDENTITY()',N'#p0 nvarchar(4000),#p1 nvarchar(4000),
#p0=N'Test',#p1=NULL
that fails because it ignores my Id and assumes (incorrectly) that Tables has an autonumber key.
This is the error:
NHibernate.Exceptions.GenericADOException: could not insert:
[Match.Models.SocietaRecord]
[SQL: INSERT INTO Match_SocietaRecord (Club, Email) VALUES (?, ?);
select SCOPE_IDENTITY()] ---> System.Data.SqlClient.SqlException:
Cannot insert the value NULL into column 'Id', table ORCHARD.dbo.Match_SocietaRecord';
column does not allow nulls. INSERT fails.
How explain to repository to look at table and do not assume identity if there is not so that I can insert my own id?
(Yes, I provided a valid Id on object record before creating)
Edit:
I have digged a bit in code and I think that:
Incorrect Identity is due to Fluent NHibernate: Auto Mapping Conventions
Orchard overrides incorrect assumpions using IAutoMappingAlteration
so I can change my question in:
How can I define a custom IAutoMappingAlteration for my class to explain Orchard that my Id is not an identity?
or (in other words)
where I can find the "automapping configuration" in orchard as described in Auto-mapping documentation?
https://github.com/jagregory/fluent-nhibernate/wiki/Auto-mapping
(... "You can modify the way the automapper discovers identities by overriding the IsId method in your automapping configuration." ... )
The repository implementation in Orchard is there to be used by higher-level content management API. It is not designed or intended as a general purpose repository implementation or a substitute for an ORM (we have nHibernate under the hoods for that). You should use nHibernate directly. Low-level examples of that in the OrchardPo module: https://bitbucket.org/bleroy/orchardpo/src

How to control primary key values when seeding data with Entity Framework codefirst

I am creating an asp.net mvc4 site using entity framework 5 with codefirst and sql server express 2012.
I have enabled migrations and now do this in my Configuration.Seed method:
(note that I want to set the primary key to 8 even though this is the first record in the database).
context.ProductCategoryDtoes.AddOrUpdate(x => x.Id,
new ProductCategoryDto() { Id = 8, Name = "category1" }
);
My Model object is defined like this:
[Table("ProductCategory")]
public class ProductCategoryDto {
public long Id { get; set; }
public string Name { get; set; }
}
This results in a table in (SQL SERVER EXPRESS 2012) where the Id column has Identity = true, Identity seed = 1, identity increment = 1.
Now when I run migrations by doing an PM> Update-Database this result in a row with Id = 1.
So my question are:
1) How can I control the values of auto incremented primary keys when seeding data.
2) If the solution is to increment the key columns seed value, then how is this to be done when I am using Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());. This will nuke and rebuild the database everytime I update the database, so how would the seed value be updated in the fresh database?
Just create dummy entities with default values, then add your real data and afterwards delete the dummies. Not the best way but I guess there is no other...
Have you tried adding this on top of your Id property:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
It seems you are trying to defeat the purpose of an identity column. If you want to do this your only choice is to use SQL Commands Set IDENTITY_INSERT to allow you to insert the value and then run DBCC CHECKIDENT to update the seed. Not a really good idea. These options have security and performance limitations.
You may want to consider using a GUID instead. You can create GUIDs in code which are guaranteed to be unique, and you can also generate GUIDs in SQL as a column default.
With GUIDs, which are non sequential you will need to think through a good indexing strategy. This approach is also debatable.
Ultimately, it looks like you need a different strategy other than using an Identity Column.
It is very hackish, but I ran into a scenario where I had to do it due to some report having hard-coded PK values. Fixing the reports was beyond my scope of work.
Context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.ProductCategoryDto ON " +
"INSERT INTO dbo.ProductCategoryDto (Id, Name) VALUES (8, 'category1') " +
"SET IDENTITY_INSERT dbo.ProductCategoryDto OFF");

Can code-first Entity Framework do cross database queries with SQL Server DBs on the same box?

I know there have been a lot of questions about Entity Framework doing cross database queries on the same server posted to stackoverflow. Mostly the answer seems to be 'no', and this link from way back in 2008 is referenced. However, Entity Framework is changing all the time and with CTP5 out, I'm wondering if the answer is still the same - that you can't do it, or you can do it if you manually edit the edmx file, or you have to use views. This feature alone is the reason I'm still tied to Linq-to-SQL, as we have multiple SQL Server 2008 databases on the same server and need to query across them. Polluting our databases with hundreds of select * views is not an option, and with code-first development I don't have an edmx file to edit. I was playing with the pubs database to see if I could get somewhere, but I'm stuck. Any suggestions?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace DbSchema {
public class Employee {
[Key]
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public short JobID { get; set; }
public Job Job { get; set; }
}
public class Job {
[Key]
public short ID { get; set; }
public string Description { get; set; }
}
public class PubsRepository : DbContext {
public DbSet<Employee> Employee { get; set; }
public DbSet<Job> Job { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
// employee
var eeMap = modelBuilder.Entity<Employee>();
eeMap.ToTable("employee", "dbo"); // <-- how do I reference another database?
eeMap.Property(e => e.ID).HasColumnName("emp_id");
eeMap.Property(e => e.FirstName).HasColumnName("fname");
eeMap.Property(e => e.LastName).HasColumnName("lname");
eeMap.Property(e => e.JobID).HasColumnName("job_id");
// job
var jobMap = modelBuilder.Entity<Job>();
jobMap.Property(j => j.ID).HasColumnName("job_id");
jobMap.Property(j => j.Description).HasColumnName("job_desc");
}
public List<Employee> GetManagers() {
var qry = this.Employee.Where(x => x.Job.Description.Contains("manager"));
Debug.WriteLine(qry.ToString());
return qry.ToList(); // <-- error here when referencing another database!
}
}
}
I think that the answer is still no, but there are ways around it.
The reason why it is no, it that EF uses a DBContext, and a context has a connection string, and a connection string goes to a database.
Here are 2 ways around it:
use 2 different contexts one against each database, this will mean bringing data to the client and merging it on the client.
use linked tables on the database, pulling data through views, so that EF sees it as coming from a single database.
In your code it looks like you are using 2 dbcontexts
There are two ways to do it.
One is, of course, to create a view in one of the databases which does the cross database query, then access the veiw from your model as you would any other view.
The other was it to create the same cross database query view within the model itself by creating a DefiningQuery. This is most similar to how you would do it with SQLClient. In SQLClient, you'd create the view in T-SQL as the text of a SQLCommand, then execute the command to create a data reader or data table. Here you use the same T-SQL to create a DefiningQuery, then link it up with an Entity that you create manually. It's a bit of work, but it does exactly what you'd want it to.
Here's a link on using DefiningQuerys: http://msdn.microsoft.com/en-us/library/cc982038.aspx.
If you happen to have the book "Programming Entity Framework" by Lerman from O'Reilly, there a good example in chapter 16.
So you have to jump through a few hoops to do what you used to do directly with SQLClient, BUT you get the modeled Entity.
The answer is still the same. If you want to execute cross database query you have to fall back to SQL and use SqlQuery on context.Database.
Warning! using DefiningQuerys can be VERY SLOW!
Here's an example:
If this is the defining query that you create an Entity against:
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
Then when you do a select against the Entity by CustomerID, the SQL trace looks something like this:
Select
[Extent1].[CustomerID] as [CustomerID],
[Extent1].[FirstName] as [FirstName],
[Extent1].[LastName] as [LastName],
[Extent1].[SalesCatetory] as [SalesCatetory]
From (
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
) as [Extent1]
Where '123456' = [Extent1].[CustomerID]
SQL Server may run this query very slowly. I had one case, a little more complicated than the above example, where I tried the DefiningQuery text directly in a SQl Server Management Console query window by adding a where clause for the value I wanted to select for. It run in less than a second. Then I captured the SQL Trace from selecting for the same value from the Entity created for this DefiningQuery and ran the SQL Trace query in a SQL Server query window - it took 13 seconds!
So I guess that only real way to do cross database queries is to create a veiw on the server.

Resources