RawSQL and auto-generated keys in EF Core 3.1 - sql

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

Related

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"

ServiceStack AutoQuery crash on synthetic field

This is a follow up on: ServiceStack AutoQuery synthetic field
.NET Core empty web template on newest 5.x versions of SS and .Net Core.
I'm trying to create an AutoQuery service that I can decorate with a few synthetic fields (i.e. fields that don't come from the database). So I've got this AutoQuery service with the following Data Transfer Object:
public class DataSource {
[Ignore]
public string Hello { get { return "Hello there"; }}
public string DataSourceId { get; set; }
public string DataSourceName { get; set; }
public int DataSourceSort { get; set; }
public bool DataSourceDisabled { get; set; }
public DateTime LastModified { get; set; }
}
When I don't include the public string Hello it all works.
The service itself is just a on-liner:
[Route("/query/datasources")]
public class QueryDatasources : QueryDb<DataSource> {}
This is the error I'm getting:
Offset0Total0Response Status
Error CodeInvalidOperationExceptionMessageSequence contains no matching elementStack Trace[QueryDatasources: 22/03/2019 17:47:41]: [REQUEST: {}] System.InvalidOperationException: Sequence contains no matching element at System.Linq.Enumerable.First[TSource](IEnumerable'1 source, Func'2 predicate) at ServiceStack.OrmLite.OrmLiteDialectProviderBase'1.GetLoadChildrenSubSelect[From](SqlExpression'1 expr) in C:\BuildAgent\work\27e4cc16641be8c0\src\ServiceStack.OrmLite\OrmLiteDialectProviderBase.cs:line 1616 at ServiceStack.OrmLite.SqlServer.SqlServerOrmLiteDialectProvider.GetLoadChildrenSubSelect[From](SqlExpression'1 expr) in C:\BuildAgent\work\27e4cc16641be8c0\src\ServiceStack.OrmLite.SqlServer\SqlServerOrmLiteDialectProvider.cs:line 587 at ServiceStack.OrmLite.Support.LoadList'2..ctor(IDbCommand dbCmd, SqlExpression'1 q) in C:\BuildAgent\work\27e4cc16641be8c0\src\ServiceStack.OrmLite\Support\LoadList.cs:line 46 at ServiceStack.OrmLite.OrmLiteReadCommandExtensions.LoadListWithReferences[Into,From](IDbCommand dbCmd, SqlExpression'1 expr, IEnumerable'1 include) in C:\BuildAgent\work\27e4cc16641be8c0\src\ServiceStack.OrmLite\OrmLiteReadCommandExtensions.cs:line 957 at ServiceStack.OrmLite.OrmLiteExecFilter.Exec[T](IDbConnection dbConn, Func'2 filter) in C:\BuildAgent\work\27e4cc16641be8c0\src\ServiceStack.OrmLite\OrmLiteExecFilter.cs:line 64 at ServiceStack.TypedQuery'2.Execute[Into](IDbConnection db, ISqlExpression query) in C:\BuildAgent\work\3481147c480f4a2f\src\ServiceStack.Server\AutoQueryFeature.cs:line 1089
The issue is because your data model doesn't have an explicit Primary Key.
In OrmLite each model must have a single primary key, by convention OrmLite expects it to be named Id and you can use [Alias("DbFieldName")] attribute it map it to a column with a different name or use the [PrimaryKey] attribute to tell OrmLite to use a different property for the primary key.
If your model doesn't have an explicit [PrimaryKey] attribute it will use the first [AutoIncrement] or [AutoId] on your model, if none of these exists it fallsback to using the first property as the primary key in which case is an ignored property.
I've removed ignored properties from consideration in this commit, however you should be explicit in which field to use as your Primary Key (using any of the above attributes or Id naming convention) instead of relying on OrmLite's fallback heuristic.

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;

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

How to tell if a property of an EntityObject is a primary key or foreign key?

Suppose that I have a class generated by Entity Framework called Student.
Student has the following properties:
Id int,
Name, string
Age, int
TeacherId int
Suppose further that Id refers to the primary key in SQL that identifies what student a Student object refers to and TeacherId is a foreign key that tells who the student's teacher is.
Suppose I want to write a function which takes any EntityObject (such as this one) as a parameter and returns information about which properties are primary keys and foreign keys.
How can I do this?
If this is not appropriate, then how can Entity Framework tell me which properties are primary and foreign keys?
For now, let's not take into consideration composite key fields.
Looking over the code that is autogenerated, I can see that primitive properties in the generated class have several attributes, and among these is an EdmScalarPropertyAttribute which has a boolean EntityKeyProperty which seems to indicate whether or not the property is a key.
How to read the values of the attributes is described in an article here: http://msdn.microsoft.com/en-us/library/71s1zwct.aspx
I'll bet that I can find a consistent pattern for how foreign keys are handled as well!
I have little support section in Repository for such questions.
See the GetEntityKeyFields method below
I dont have check routine already written FK, but if you check the entityField details, it will be there as well.
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); } // cant be in core interface since it is EF types
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
// cant be in core interface since it is EF types
public BosBaseDbContext Context { get; protected set; }
public EntityState GetEntityState(T entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public string[] GetEntityFields() { return GetObjectSet().EntitySet.ElementType.Properties.Select(e => e.Name).ToArray(); }
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}