Error when using GUID with Entity Framework - c#

My SQL table contains an Id column which is of datatype uniqueidentifier. As per advice found on SO, I've set it to have a default value of (newid()) .
Using Entity Framework 4.2 with code first, I've then mapped my Guid property to the relevant field in SQL:
[Key]
public Guid Id { get; set; }
However, whenever I try to insert an entity I receive the following exception:
The type of one of the primary key values did not match the type defined in the entity.
The argument types 'Edm.Guid' and 'Edm.String' are incompatible for this operation. Near WHERE predicate, line 1, column 61.
The only solution I can find on both here and Google is to add [DatabaseGenerated(DatabaseGeneratedOption.Identity)] as a data annotation on my Id. This doesn't change a thing - so what else could be causing this error?
Thanks in advance.

have you try set identify StoreGeneratedPattern ?
you could do it in OnModelCreate Method.
Model.Entity<Foo>().Property(o => o.Id).HasDatabaseGenerationOption(DatabaseGeneratedOption.Identity);

Using a Guid as a primary key is ill-advised if you need performance and have an index over it. The Guids are being generated with randomized values, so every time you insert a new item it will cause more work for SQL to update the index than if you used a bigint (it has to insert into different parts of the index each time instead of always appending to the end). If you need a unique identifier (such as to link items across systems that don't share the same database) then simply add it as an additional column on the table (and if you are paranoid and willing to take a perf hit on insert/update, you could additionally add a unique constraint over it).

Related

EF Code-first, how to insert primary key to another field when inserting?

This is my entity that I will insert into the database:
public sampleEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
[Required]
public string Ticket { get; set; }
}
Ticket is in the format like
string.Format("{0:yyyyMMdd}-{1}", DateTime.Now, PrimaryKey);
So when I add this entity to the context, primaryKey always is 0, so Ticket value always be '20170315-0'
For now my solution is
// first add row and save
SampleEntity sample = new SampleEntity {
Ticket=DateTime.Now.ToString("yyyyMMdd")
};
context.Samples.Add(sample);
context.SaveChanges();
// then find it out and update
var latest = context.Samples.OrderByDecending(p => p.PrimaryKey).First();
latest.Ticket += sample.PrimaryKey.ToString();
context.SaveChanges();
How can I set Ticket value according to primaryKey value when SaveChanges() without update?
You have DatabaseGeneratedOption.Identity option for primary key.
That means that only SQL Server know that ID, and that knowledge materializes only in the moment of the actual INSERT into database (as related column in database is some kind of IDENTITY column).
To understand that think, for example, of two applications which are inserting new records into database simultaneously - they will receive different keys, but you cannot know for sure which application receive which key.
Entity Framework will generate two requests for SaveChanges - first one is INSERT, and another one is SELECT to receive that generated key.
Only after that your code will know actual key and can be able to use it for your ticket calculations - so basically you cannot avoid another UPDATE with EF.
What you can do though is to change Primary Key type for something controlled by your code rather than by database - for example, random GUID; in this case you will know ID before insert and can use it in any way you want.
But using say GUID for primary key results in other complications, which in most cases won't worth it, like non-sequential insert results in often index rebuilds, still some probability of keys collision, more space to keep column etc.
Another option would be to have calculated column OR similar logic for ticket column in application, so you will have separate Date column and separate Id column, but for ticket you will either always apply concat logic whenever you need it, of create calculated column which will only return values (and thus will be read-only for database and EF).

How to auto-populate Sequential Guid default value

Setup
I have a uniqueidentifier column that's set as the primary key with a Default Value or Binding of (newsequentialid()).
What Works
In my MVC project, I can use things like [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] and Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); to allow SQL Server to take over and use the specified Default Value or Binding to automatically generate a Sequential Guid, and this works fine.
What Doesn't Work
However, if I write code outside of my MVC project, specifically, in LINQPad, an empty Guid, e.g., 00000000-0000-0000-0000-000000000000, is always inserted:
Resources.InsertOnSubmit(new Resource());
SubmitChanges();
I understand that I can write something like this to insert a randomized Guid value: new Resource() { Id = Guid.NewGuid() }, but I need a Sequential Guid.
Why I'm Confused
I was under the impression that when a null or empty value was passed to a SQL Server column, the Default Value or Binding, if specified, would automatically be used.
Question #1:
Why is the Default Value or Binding being ignored when using LINQPad?
Question #2:
How can I make SQL Server use the specified Default Value or Binding of (newsequentialid()) when I don't have access to the MVC attributes noted above?
With regards to your second question, SQL Server will ignore the default binding on your column if NULL is explicitly specified in the insert statement. Similarly, if your MVC project is generating a default Guidof 00000000-0000-0000-0000-000000000000 then SQL Server will simply insert the value into your table.
One option is to simply generate your own sequential GUID from your MVC project. There are plenty of example of sequential GUID generators in C#. I personally like Alex Siepman solution for sequential GUID you can find it at the following location:
http://www.siepman.nl/blog/post/2015/06/20/SequentialGuid-Comb-Sql-Server-With-Creation-Date-Time-.aspx
Alternatively, you can create INSTEAD OF INSERT trigger on your target table to correctly handle the insert in the manner you want. In your case you want to omit the ID being passed to the table and let SQL Server generate the ID for you. The trigger will look as follow:
CREATE TRIGGER AutoGuid_Trigger ON [dbo].[YourTableName]
INSTEAD OF INSERT AS
BEGIN
INSERT INTO [dbo].[YourTableName]
(
-- Exclude ID from column list, SQL Server will generate the ID
[YourCol1]
,[YourCol2]
,[YourCol3]
,...
)
SELECT [YourCol1]
,[YourCol2]
,[YourCol3]
,...
FROM inserted
END

Is it possible to get Identity Field value before saving it in entity framework

I have a customer and sales table
CUSTOMER
--------------
Id (int auto increment)
Name
SALES
---------------
Id (int auto increment)
CustomerId (int)
OrderTotal (decimal)
With Guid i can do this.
dbTransaction = dbContext.Database.BeginTransaction(isolationLevel);
var customer = new Customer()
{
Id = Guid.NewGuid(),
Name = "John Doe"
};
var sales = new Sales()
{
Id = Guid.NewGuid(),
CustomerId = customer.Id,
OrderTotal = 500
};
dbContext.SaveChanges();
dbTransaction.Commit();
How can i do this if my primary key is int (with DatabaseGeneratedOption.Identity)?
You cannot. The ID that goes into a IDENTITY column is generated by the database upon insertion, and all "tricks" to circumvent that and determine the ID yourself are probably flawed.
Short answer: If you want some say in generating an ID before you save, use a GUID (UNIQUEIDENTIFIER), or a SEQUENCE (if you're working with SQL Server 2012 or newer).
Why you should not compute the next free ID yourself:
Don't even consider running a query such as context.Customers.Max(c => c.Id) + 1 as a viable solution, because there's always the possibility that you have concurrent database accesses: another process or thread might persist a new entity to the same table after you've read the next "free" ID but before you store your entity. Computing the next free ID will be prone to collisions, unless your whole operation of getting the ID, doing something with it, and storing the entity with that ID were atomic. This would likely require a table lock in the DB, which might be inefficient.
(The same problem exists even when you use SEQUENCEs, a new feature introduced in SQL Server 2012.) (I was wrong; see end of answer.)
Possible solutions:
If you need to determine the ID of an object before you save it, then don't use the ID that goes in a IDENTITY column. Stay with a GUID, because you're extremely unlikely to get any collision with these.
There's no need to chose between one or the other: you can actually have your cake and eat it! Nothing stops you from having two ID columns, one that you determine externally (the GUID) and one that stays internal to the DB (the IDENTITY column); see the blog article "CQS vs. server generated IDs" by Mark Seemann for a more detailed look at this idea. Here's the general idea by example:
CREATE TABLE Foos
(
FooId INT IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
-- ^^^^^ assigned by the DBMS upon insertion. Mostly for DB-internal use.
Id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE DEFAULT (NEWID()),
-- ^^ can be dictated and seen by the users of your DB. Mostly for DB-external use.
…
);
CREATE TABLE FooBars
(
FooId INT NOT NULL FOREIGN KEY REFERENCES Foos (FooId),
-- use DB-internal ID in foreign key constraints ^^^^^
…
);
CREATE VIEW PublicFoos AS
SELECT Id, … FROM Foos;
-- ^^ publish the public ID for users of your DB
(Make sure you adhere to some convention for consistently naming internal and public ID field names.)
SEQUENCEs, a feature introduced in SQL Server 2012, are a possible alternative to having an IDENTITY column. They are automatically increased and you are guaranteed a unique number when getting the next free ID using NEXT VALUE FOR SomeSequence. One of the use cases mentioned on MSDN are:
Use sequences instead of identity columns in the following scenarios: […] The application requires a number before the insert into the table is made.
Some caveats:
Getting the next sequence value will require an additional roundtrip to the database.
Like identity columns, sequences can be reset / re-seeded, so there is the theoretical possibility of ID collisions. Best to never re-seed identity columns and sequences if you can help it.
If you fetch the next free sequence value using NEXT VALUE FOR, but then decide not to use it, this will result in a "gap" in your IDs. Gaps obviously cannot happen with regular (non-sequential) GUIDs because there is no inherent ordering to them.
As far as I know you can not get the ID before saving the changes in the database. The database creates the ID after the values are inserted in the database.
To add to it when you call .SaveChanges() then only it will write the changes to the database and only then the identity value will get generated.
You can get that value by a small hack.
Create a function in SQL Server something like this
CREATE FUNCTION fn_getIdentity(#tbl_name varchar(30))
AS
BEGIN
IF #tbl_name = 'Employee_tbl'
RETURN IDENT_CURRENT('Employee_tbl')
ELSE IF #tbl_name = 'Department_tbl'
RETURN IDENT_CURRENT('Department_tbl')
ELSE
RETURN NULL
END
Create an entity in your Entity framework to support this function and use it wherever you want.
Then use
var nextValue = dbContext.fn_getIdentity("Employee_tbl")
IDENT_CURRENT returns you last incremented value for an identity column. This doesn't mean MAX + 1 as if your previous transaction generated an identity value for this column but was rolled back then you will see next value that will be generated.
Please note, I didn't check syntax properly and this syntax is just to present an idea.
However I would go with solution provided by Stakx i.e. SEQUENCE if using SQL Server 2012 or above
else creating a table to implement functionality of SEQUENCE by reserving ID once generated permanently in a table.
We can indeed if your ID is in integer, using SQL. The following example is for PostreSQL, please feel free to adapt it for other servers and to edit this answer.
Create a virtual entity model for the database to wrap our query result and to have a fake DbSet<some virtual model> to use ef core extension method FromSqlRaw.
Define a virtual model:
public class IntReturn
{
public int Value { get; set; }
}
Now fake a DbSet<IntReturn> it will not be really created on server:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<IntReturn>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
Now we can get the currently used Id for Customers table in this example. The calling method is inside a Subclassed : DbContext, you'd what to instantiate your context to use it instead of this:
public int GetNextCustomerId()
{
//gets current id, need do +1 to get the next one
var sql = "Select last_value as Value FROM \"Customers_Id_seq\";";
var i = this.Set<IntReturn>()
.FromSqlRaw(sql)
.AsEnumerable()
.First().Value + 1;
return i;
}
Credits to:
https://erikej.github.io/efcore/2020/05/26/ef-core-fromsql-scalar.html
https://stackoverflow.com/a/18233089/7149454

LINQ to Entities returning incorrect results for tables without primary key

I have found that LINQ to Entities needs a primary key on the table in order to return correct results. Without it, I get the expected number of rows but including duplicates (and accordingly, missed rows). This problem is described here and here, and I consider it to be a bug.
In one of my tables, each row is unique but I cannot create a compound key across all fields because nullable columns cannot be used in primary keys (again, I consider this a SQL Server limitation).
So... how can I get correct results when selecting from this table using LINQ to Entities? I believe the "key" may be to create an "Entity Key" across all columns in the Visual Studio model designer but I'm not sure how to do this. Setting Entity Key = true on nullable columns throws an exception.
At one stage I gave up and added an identity int column with auto-increment enabled and used that as PK, which solved the issue, but I had to throw this out because of the volume of data being deleted/inserted all the time (it's not possible to simply reset the auto-increment counter on a schedule because not all of the rows are deleted, causing clashes).
My last resort will be to add a bigint identity column as PK, get rid of the auto-increment seed value resetter and hope it lasts "long enough" for the life of the application, but I'm not comfortable with this. (Edit: OK... it will last long enough. My main concern is performance)

Entity Framework One-To-Many Insert - Foreign Key violation

I'm using Entity Framework for the first time and I'm trying to create a object with a collection (and I want all the objects in the collection to be created in database as well) but I'm having some foreign keys violations.
My sample tables:
table APPOINTMENTS: ID, VAR1, DATE_APPOINTMENT
table GUESTS: ID, APPOINTMENT_ID, USER_ID, VAR2, VAR3
My test code:
DomainService aux = new DomainService();
APPOINTMENTS appointment = new APPOINTMENTS();
appointment.VAR1 = "BLA";
appointment.DATE_APPOINTMENT = new DateTime();
//The user with id = 1 is already created in the database
appointment.GUESTS.Add(new GUESTS { USER_ID = 1, VAR2 = 1, VAR3 = "F" });
aux.InsertAppointment(appointment);
At DomainService I have:
public void InsertAppointment(APPOINTMENTS appointment)
{
using (var context = this.ObjectContext)
{
context.AddToAPPOINTMENTS(appointment);
context.SaveChanges();
}
}
But I'm getting this error:
{"ORA-02291: integrity constraint (FK_GUESTS_APPOINTMENTS) violated - parent key not found"}
What am I doing wrong?
UPDATE:
To create the ID's in the database, I am using a sequence for each table and a trigger before insert to get the next value.
When I create a single object, e.g. a appointment without guests, it inserts in the database and it generates the id.
The solution to this problem:
"The ID fields that are generated from sequences won't be handled
correctly. After saving the entities the ID's will be returned as 0.
I'll fix this by manually hacking the SSDL (open your .edmx file in a
text editor) with StoreGeneratedPattern="Identity" attributes on the
ID fields (lines 6 and 16). Note that designer may rip that change out
upon future modification.
While I suppose it's not absolutely necessary it might also be prudent
to modify some type metadata such as changing "number"s to "int"s in
your SSDL and "Decimal"s to "Int32"s in your CSDL where applicable.
Frequently these don't auto-generate with the desired values
especially with XE."
#http://www.chrisumbel.com/article/oracle_entity_framework_ef
As for me, the problem was solved simply by opening diagram .edmx and changing property StoreGeneratedPattern from None to Identity for each Primary Key in each table. After saving everything was fine.
I'm using VS 2012, Entity Framework 5 (6 is not supported yet), Oracle 11.2, last ODP.NET 12, .Net 4.5
In case of EF code first approach, if this error come
(ORA-02291: integrity constraint (FK_GUESTS_APPOINTMENTS) violated -
parent key not found)
In my case there are 2 tables which have Identity columns. So I just added
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
property to my model class just above the the column which is identity column in database and it solved my problem :)
Hope this help :)
I cant see where you are setting your Primary Key (the ID property of the appointment class). Are you using a key generator on the database side? If not this should be the problem.
You're inserting a record with a foreign key value that is not found in the parent table that constraint refers back to.

Categories

Resources