How to tell Entity Framework that my ID column is auto-incremented (AspNet Core 2.0 + PostgreSQL)? - asp.net-core

Code is simple.
Tag.cs entity:
public partial class Tag
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
HomeController.cs class:
public async Task<IActionResult> Index()
{
tagRepository.Insert(new Tag
{
Name = "name",
Description = "description"
});
await UnitOfWork.SaveChangesAsync(); // calls dbContext.SaveChangesAsync()
return View();
}
TagRepository.cs class:
// Context it's a usual DBContext injected via Repository's constructor
public virtual void Insert(TEntity item)
=> Context.Entry(item).State = EntityState.Added;
Tag table was created by running:
CREATE TABLE Tag (
ID SERIAL PRIMARY KEY,
Name text NOT NULL,
Description text NULL
);
When run my application I get an error:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (28ms) [Parameters=[#p0='?', #p1='?', #p2='?'], CommandType='Text', CommandTimeout='30']
INSERT INTO "tag" ("id", "description", "name")
VALUES (#p0, #p1, #p2);
Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "tag_pkey"
at Npgsql.NpgsqlConnector.<DoReadMessage>d__157.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
at Npgsql.NpgsqlConnector.<ReadMessage>d__156.MoveNext()
As you can see Entity Framework tries to send id=0 value to the DB, but there is already a record with id=0 in my DB and that's why it throws duplicate key error.
I didn't find any answer so far and my question is: how can I get rid of this error and tell Entity Framework that my id column is auto-incremented and there is no need to update it?

You have to use here "ValueGenerationOnAdd()". As the issue you are getting is already reported on GitHub. Please find the below link.
https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/73
You can find more info regarding Generated Value pattern from following link.
Value generated on add
public classs SampleContext:DBContext{
public DbSet<Tag> Tag { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Tag>()
.Property(p => p.ID)
.ValueGeneratedOnAdd();
}
public class Tag{
public int Id { get; set; }
public string Name { get; set; }
public string Description{get;set;}
}
}
Source:- https://www.learnentityframeworkcore.com/configuration/fluent-api/valuegeneratedonadd-method
Hope this will help

According to the Postgre SQL docs here is an example which shows how to achieve the desired result:
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<Blog>().Property(b => b.Id).UseIdentityAlwaysColumn();

After a lot of wasted time on research I've solved it absolutely unexpected. Actually, the solution to my problem lies on the surface, because the problem is not in EF and not in it's stuff. In my case it was SEQUENCE. I mean, involved table has serial column and related sequence just was changed by some side-effect. In another words - sequence was restarted from 1 and at the same time the table is already having values with 1, 2, 3 ... etc. That's why postgres throws 'duplicate key' error. So, the solution is:
ALTER SEQUENCE "your_table_seq" RESTART WITH your_value;

Related

RawSQL and auto-generated keys in EF Core 3.1

I have a Model with a Guid primary key. I want the Database to generate a key on insert so I added the following annotations:
public class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid employee_id { get; set; }
[Required]
public int employee_number { get; set; }
//more props...
}
Now I expected that inserts with RawSQL wouldn't expect a primary key, however the folllowing statement doesn't work when executred through ExecuteSqlRaw:
INSERT INTO employees (employee_number/**, more props*/)
VALUES (123 /**,more props*/)
An error is caused by the DB about a non-nullable primary key. Explicitly inserting some random Guid works, but i figured the DB would take care of this.
Is the Identity annotation wrong?
You could miss one step.
When I add migration, ef core will generate a migration file. Then defaultValueSql: "newsequentialid()" need to be added here.
After excuting Update-Database, I can insert the record with RawSQL.
Edit:
Another method to use HasDefaultValueSql in DbContext.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Property(b => b.employee_id)
.HasDefaultValueSql("newsequentialid()");
}

Entity Framework Core: using navigation properties without foreign key

I have following object model:
public class SharingRelation:BaseEntity
{
public Guid? Code { get; set; }
public string Value { get; set; }
}
public class SecondLevelShareEntity : BaseEntity
{
public string Name { get; set; }
public Guid? SharingCode { get; set; }
public List<SharingRelation> SharingRelations { get; set; }
}
In my database (it may be poor db design but I need to answer this question for research), SharingRelation is some sort of dependent entity of SecondLevelShareEntity on Code == SharingCode values. I can have two entities of type SecondLevelShareEntity with same SharingCode value. So, for each of them I need to get all related SharingRelation objects depending on Code and SharingCode values. I can do it using SQL and join on this columns. But how can I do it using EF Core and navigation properties (I want to get all dependent entities using Include() for example)? When I configure my entities like this
public class SharingRelationEntityTypeConfiguration : BaseEntityTypeConfiguration<SharingRelation>
{
public override void Configure(EntityTypeBuilder<SharingRelation> builder)
{
base.Configure(builder);
builder.HasOne<SecondLevelShareEntity>().WithMany(x => x.SharingRelations).HasForeignKey(x => x.Code)
.HasPrincipalKey(x => x.SharingCode);
}
}
EF Core creates foreign key and marks it unique. I am obviously getting an error that that is impossible to have several SecondLevelShareEntity with the same SharingCode
System.InvalidOperationException : The instance of entity type 'SecondLevelShareEntity' cannot be tracked because another instance with the key value '{SharingCode: 8a4da9b3-4b8e-4c91-b0e3-e9135adb9c66}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
How can I avoid creation of foreign key, but keep using navigation properties (as far, as I see normal queries with navigations generate simple JOIN statements)
UPDATED I can provide real data in database. SecondLevelShareEntity table looks like this:
_id Name SharingCode
----------------------------------------------------------------------
1 "firstSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
And SharingRelation table looks like this:
_id Value Code
----------------------------------------------------------------------
1 "firstSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"

How to have a primary key with a different name than "Id"

I have the following situation in my ASP.NET Core application with Entity Framework Core 1.1
Database-Table named "Content"
Content_ID (int not null, primary key)
Title (varchar max)
Description (varchar max)
Model ("ContentEntry.cs"):
public class ContentEntry
{
public int Id {get; set;}
public string Title {get; set;}
public string Description {get; set;}
}
Configuration File (ContentEntryConfiguration.cs)
public class ContentEntryConfiguration : IEntityMappingConfiguration<ContentEntry>
{
public void Map(EntityTypeBuilder<ContentEntry> modelBuilder)
{
modelBuilder.HasKey(m => m.Id);
modelBuilder.Property(m => m.Id).HasColumnName("Content_ID");
modelBuilder.Property(m => m.Title ).HasColumnName("Title");
modelBuilder.Property(m => m.Description).HasColumnName("Description");
modelBuilder.ToTable("Content");
}
}
As mentioned above, the primary key of my table is named "Content_ID".
When I execute a LINQ query, I receive an error saying that the column "ID" hasn't been found on the database. After inspecting the generated query with the SQL Profiler, I noticed that the query contains "ID" instead of "Content_ID".
I expect entity framework to generate a query containing the column "Column_ID" instead and map it to my model-property named "Id".
Do you have an idea why this is happening and how I could fix this issue?
For people with simpler needs, this will suffice
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ContentEntry>().Property(c => c.Id).HasColumnName("Content_ID");
}
I solved it, I just forgot to register the entity mapping in my DB Context Class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.RegisterEntityMapping<ContentEntry, ContentEntryConfiguration>();
}

BlToolkit insert data failure in BaseRepository class

I'm using BaseRepository in asp .Net MVC project. Edit operation works but in Add operation, I should make a trick to make it work. In detail, my base repository and BaseEntity classes:
public class BaseRepository<TEntity, T> : IRepository<TEntity, T> where TEntity : BaseEntity<T>
{
private DbManager _context;
private Table<TEntity> Table
{
get { return _context.GetTable<TEntity>(); }
}
public BaseRepository(DbManager context)
{
_context = context;
}
//...
public TEntity Add(TEntity entity)
{
//...
return entity;
}
public TEntity Edit(TEntity entity)
{
_context.Update(entity);
return entity;
}
//...
}
public class BaseEntity<T>
{
[PrimaryKey]
public T Id { get; set; }
}
I tried three ways for Add operation to make it work. First two ways gave errors.
First way(Doesn't work):
public TEntity Add(TEntity entity)
{
_context.Insert(entity);
return entity;
}
Error Message:
Cannot insert explicit value for identity column in table '...' when IDENTITY_INSERT is set to OFF.
--
Second way(Doesn't work):
public TEntity Add(TEntity entity)
{
Table.Insert(() => entity);
return entity;
}
Error Message:
Operation is not valid due to the current state of the object.
--
Third way(Working):
public TEntity Add(TEntity entity)
{
var l = new List<TEntity> { entity };
_context.InsertBatch(l);
return entity;
}
--
Edit operation works without error, but for Add operation I need to make some trick. What is the problem with normal Add operation and, is there a way to make it work?
I tried advice of #Mladen Macanović and I added Identity attribute to primary key in base BaseEntity class, then errors shown above gone for entities having int type primary key.
Errors shown above gone for entities having int type of primary key:
public class BaseEntity<T>
{
[PrimaryKey Identity]
public T Id { get; set; }
}
But, this is not a full solution because, some of my entities have primary key in type of Guid, so adding Identity attribute to them gives another error.
InsertBatch method works without using Identity attribute. So, you can add data without using Identity attribute in BaseEntity class for Identity column. What is the difference of insertbatch method? How can I resolve errors shown above without using InsertBatch method?
The problem is with your database table. That is why you're getting the IDENTITY_INSERT error. Go to the SQL Server Management Studio, right click on the table, Design, and for the primary key column set the property Identity Specification -> (Is identity) to Yes.

NHibernate, a different object with the same identifier value was already associated with the session

I have been working with NHibernate, using Fluent NHibernate for mapping. I solved a lot of issues, and started to think myself as experienced in nhibernate.
However, this error is quite strange.
This is my model:
public class MessageNew
{
public virtual int Id { get; set; }
public virtual string Content { get; set; }
public virtual string Subject { get; set; }
public virtual User User { get; set; }
public virtual bool IsSent { get; set; }
public virtual string AmazonMessageId { get; set; }
}
And my mapping
public class MessageNewMap : ClassMap<MessageNew>
{
public MessageNewMap()
{
Id(x => x.Id);
Map(x => x.Content).CustomSqlType("text");
Map(x => x.Subject);
Map(x => x.AmazonMessageId);
Map(x => x.IsSent);
References(x => x.User);
}
}
Here where exception occurs:
foreach (var userToSend in usersToSend)
{
string body = MailHelper.BuildSomeBody()
if (userToSend != CurrentUser)
{
MessageNew message = new MessageNew
{
User = userToSend,
IsSent = false,
Content = body,
Subject = subject
};
session.Save(message); // Exception thrown
}
}
The exception details:
NHibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: 1779, of entity: Models.MessageNew
at NHibernate.Engine.StatefulPersistenceContext.CheckUniqueness(EntityKey key, Object obj)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
Id generator is database driven auto-increment id generator. (not hilo or any other). NHibernate version is 3.2.0 .
I have tried overloading Equals and GetHashCode, no luck.
The UnitOfWork pattern I am using requires not to commit transaction or flush session inside foreach loop. NHibernate says there is another object with same id, but all i am doing is inserting a new object, which does not have any identifier at all.
I am using the same structure all over my project, and it works well everywhere but this. I am suspicious that it might be because of "Content" property, which is text and set to a large string.
What am i missing here? Or NHibernate is missing something?
Sometimes it happend when we assign the object to the same new object. So first check your model and viewmodel that they are not same.
I had similar problem. I went through a lot of discussions, tutorials and forums, but after writing some unit tests, I realized:
1) session.Contains method works with instances
2)session.Save/SaveorUpdate works with ID
This error shows you have another instances of object with same ID in session.So, contains return false because you are working on different instances and Save/SaveorUpdate throws an exception because there is another object with same ID in session.
I've solved my problem like this(my problem was in Job Entity):
Job lJob = lSession.Load<Job>(this.ID);
if(lJob.ID==this.ID)
lSession.Evict(lJob);
lSession.SaveOrUpdate(this);
I hope it helps you
You can use Evict() to evict an object from a session and then you can do whatever you want.
This error occurs when you have the same object in another session.
messagenew should implement Equals and GetHashCode
public class MessageNew
{
public virtual int Id { get; set; }
public override bool Equals(object obj)
{
var other = obj as MessageNew;
return (other != null) && (IsTransient ? ReferenceEquals(this, other) : Id == other.Id;
}
private int? _cachedHashcode; // because Hashcode should not change
public override int GetHashCode()
{
if (_cachedHashcode == null)
_cachedHashcode = IsTransient ? base.GetHashCode() : Id.GetHashCode();
return _cachedHashcode.Value;
}
public bool IsTransient { get { return Id == 0; } }
}
I read some NH code. It basically inserts the new instance into the database to get its id. Then it checks if the id generated by the database is actually unique. If not, you get this exception.
Your database is not generating unique ids. You most probably forgot to set it to an IDENTITY column.
OR the identity starts counting on 0 instead of 1.
That exception usually indicates that you have 2 separate instances of an object with the same identifier value which you are trying to manage over the same session.
You already have another instance of the entity with that id.
Two possible issues:
1 - Your comparison of the entity does not work. You could override equals as suggested or you could change your test case that you use prior to the save:
if (userToSend.Id != CurrentUser.Id)
2 - You are not generating a unique Id for your entity, you need to either assign an Id yourself, have NHibernate generate one or have your sql server do it for you. In your mapping it is implied that an Identity should be used (Fluents default) but have you set up the column in your database to be and Identity column?
My take: you are not declaring an Id generator. Therefore, as soon as you get two MessageNew instances in the session, they'll both have 0 as the Id.
maybe a bit late but hope this helps.
I had a similar problem when i was trying to save multiple instances of an object over the same session with an auto generated column on them. My solution was giving a diferent value and assign it mannually for each entity, so nhibernates doesn't recognize it as the same primary key for that entity.
[..]
};
session.Clear();
session.Save(message);
Try this, helped me.
Add below two lines before Session.Save or Session.SaveOrUpdate
Session.Clear();
Session.Flush();
This will clear all cached entities with the Session.