How to have a primary key with a different name than "Id" - asp.net-core

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>();
}

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 tell Entity Framework that my ID column is auto-incremented (AspNet Core 2.0 + PostgreSQL)?

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;

Set identity seed in fluentnhibernate

Using NHibernate you can set an identity seed like so:
<column name="Column1" not-null="true" sql-type="int IDENTITY(1,1000)"/>
The FluentNHibernate IdentityPart has CustomType and SqlCustomType methods, neither does it for me though. Is there a way to fluently set an identity seed?
More info:
When I do this: Map(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: The entity 'Customer' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).
When I do this: Id(x => x.Id).Column("CustomerId").CustomSqlType("int IDENTITY(1,1000)");
I get this error: More than one column IDENTITY constraint specified for column 'CustomerId', table 'Customer'
Using FluentNHibernate 1.2.0.712.
I was able to duplicate that xml by doing something like this:
Map(x => x.LoginName, "Column1").CustomSqlType("int IDENTITY(1,1000)");
Edit:
If you can't achieve what you are wanting maybe you should explicitly map this using xml for now.
There is the article at the link below about implementing custom identity generator (see: Part 1: Inheriting from TableGenerator class) but the example throws the exception for SQLite database ("SQLite errorr no such table: hibernate_unique_key"). Thus as regard SQLite there is no possibility to gain current id key from a table. It uses class TableGenerator from NHibernate API (NHibernate.Id);
http://nhforge.org/wikis/howtonh/creating-a-custom-id-generator-for-nhibernate.aspx
To avoid the exception I implemented another solution (especially the way of getting current Id). It takes advantage of Fluent-NHibernate API (GeneratedBy.Custom()). Look at the following source code:
public class MyAutoincrement<T> : IIdentifierGenerator where T : IId
{
#region IIdentifierGenerator Members
public object Generate(ISessionImplementor session, object obj)
{
NHibernate.ISession s = (NHibernate.ISession)session;
int seedValue = 1000;
int maxId = -1;//start autoincrement from zero! (fluent nhibernate start from 1 as default)
List<T> recs = s.Query<T>().ToList<T>();
if (recs.Count > 0)
{
maxId = recs.Max(x => x.getId());
}
return seedValue + maxId + 1;
}
#endregion
}
//Interface for access to current Id of table
public interface IId
{
int getId();
}
//Entity
public class MyEntity : IId
{
public virtual int Id { get; protected set; }
public virtual string MyField1 { get; set; }
public virtual string MyField2 { get; set; }
#region IId Members
public virtual int getId()
{
return this.Id;
}
#endregion
}
//Entity Mapping
public class MyEntityMap : ClassMap<MyEntity>
{
public MyEntityMap()
{
Id(x => x.Id).GeneratedBy.Custom<MyAutoincrement<MyEntity>>();
Map(x => x.MyField1);
Map(x => x.MyField1);
}
}
It works with SQLite database and involves custom identity seed.
Regards
Bronek

Setting CustomSqlType on References

I have a situation where my primary key is a char(2) in SqlServer 2008, and I want to reference it in a one-to-many relationship, but the ManyToOneBuilder (which is returned by ClassMap<>.References()) doesn't have a CustomSqlType() method. Specifically:
public class State
{
// state FIPS code is 2 characters
public virtual string StateCode { get; set; }
public virtual ICollection<County> { get; set; }
}
public class County
{
// state-county FIPS code is 5 characters
public virtual string StateCountyCode { get; set; }
public virtual State State { get; set; }
}
public class StateMap : ClassMap<State>
{
public StateMap()
{
Id(e => e.StateCode).CustomSqlType("char(2)").GeneratedBy.Assigned();
}
}
public class CountyMap : ClassMap<County>
{
public CountyMap()
{
Id(e => e.StateCountyCode).CustomSqlType("char(5)").GeneratedBy.Assigned();
References(e => e.State, "StateCode")
// Here's what I want to do, but can't because the method is not
// implemented on the class ManyToOneBuilder:
.CustomSqlType("char(2)");
}
}
Is there any way to accomplish this without modifying the ManyToOneBuilder? Is there a way to automatically map the FK (i.e. County.StateCode) to the correct type? It's trivial to add CustomSqlType to ManyToOneBuilder, but is that the right thing to do?
Keep your mapping definition as is, add your "State" column definition with
.CustomSqlType("char(2)")
and set for this column Insert=false and update=false.
I've the same problem and in AutoMapping I use this code:
mapping.Map(x => x.IdUniArticolo)
.CustomSqlType("varchar(50)")
.Not.Insert().Not.Update();
mapping.References(x => x.Articolo)
.Column("IdUniArticolo").PropertyRef("IdUniArticolo");
Keep in mind that if NHibernate itself doesn't support it, then Fluent NHibernate can't, and I don't NHibernate supports the scenario you have. I had a similar problem in that I had a 2 column composite key on a table and on one of the fields, I wanted to use an enumerated type which had a custom IUserType to translate it to its appropriate code value in the DB. Couldn't do it, so I was stuck keeping the property of the string type rather than the enumerated type.