I have an entity with a composite, primary key (Id and TreeVersion). I have another entity with values that reference this key.
First entity:
class ClassificationType
{
[Key]
[Column(Order = 1)
public int Id {get;set;}
[Key]
[Column(Order = 2, Type="varchar")]
[StringLength("100")]
public string TreeVersion {get;set}
}
Second entity:
class ClassConfig
{
public int Id {get;set;}
public string Name {get;set;}
public int? ClassificationTypeId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(10)]
public string TreeVersion { get; set; }
}
With this in place, everything works fine - I can seed my tables with data and everything is peachy.
However, when I try to add a navigation property (and thereby foreign key from second entity to first entity), seeding fails.
I add this line to second entity:
public virtual ClassificationType ClassificationType {get;set;}
This causes this migration step:
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "Id", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "Id", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
Notice how it actually fails in deducting the foreign key columns, so I manually change this to
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
This creates the following SQL statement:
ALTER TABLE [dbo].[ClassConfig] DROP CONSTRAINT [PK_dbo.ClassConfig]
ALTER TABLE [dbo].[ClassConfig] ALTER COLUMN [Id] [int] NOT NULL
ALTER TABLE [dbo].[ClassConfig] ADD CONSTRAINT [PK_dbo.ClassConfig] PRIMARY KEY ([Id])
CREATE INDEX [IX_ClassificationTypeId_TreeVersion] ON [dbo].[ClassConfig]([ClassificationTypeId], [TreeVersion])
ALTER TABLE [dbo].[ClassConfigEntries] ADD CONSTRAINT [FK_dbo.ClassConfig_dbo.ClassificationTypes_ClassificationTypeId_TreeVersion] FOREIGN KEY ([ClassificationTypeId], [TreeVersion]) REFERENCES [dbo].[ClassificationTypes] ([Id], [TreeVersion])
Now, when I seed my tables, the script fails. The message is this:
Cannot insert explicit value for identity column in table
'ClassConfigEntries' when IDENTITY_INSERT is set to OFF.
Can anyone tell me why this is happening? The values I am trying to insert into the ClassConfig table are present in the ClassificationType table at point of insertion.
I have about 30 existing tables in out model, some of them with identity specified, and all of them work as they should when seeding and navigation properties are available when getting values from the database. It is only this corner of my model, that causes me head aches.
Related
I create a table with primary key.
I tried to insert new data with entityframework6, but it would get 23502 error.
But I add the default value to the column before I insert it.
I don't understand why it would get this error.
Table DDL:
CREATE TABLE ERRORLOG(
id numeric NOT NULL,
message varchar(50) NULL,
CONSTRAINT pterrorlog_pk PRIMARY KEY (id)
);
Model:
public partial class ERRORLOG
{
[Key]
[Column(Order = 0)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
Funcation:
using (DbContext Db as new DbContext)
using (TransactionScope transactionScope = new TransactionScope())
{
ERRORLOG iLog = new ERRORLOG();
iLog.MESSAGE = Message;
Db.ERRORLOG.Add(iLog);
Db.SaveChanges(); //Get 23502 error
}
Here is the insert script, it looks like didn't insert the id, why is that?
INSERT INTO "pterrorlog"("message") VALUES (#p_0) RETURNING "id"
Edit:
After I add this script on the Model, it works fine now.
public partial class ERRORLOG
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
Looks like Entity Framework auto insert a value to the column.
After I add the script to prevent this issue, it works fine now.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
Model would like:
public partial class ERRORLOG
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
You can use PGAdmin to profile the SQL that EF is actually attempting to execute on SaveChanges. C# is case sensitive while Postgres defaults to lower case. If I recall NPGSQL will format all EF SQL Queries with double-quotes so if your Entities were declared with properties like ID, it would be generating statements like INSERT INTO "ERRORLOG" ( "ID", "MESSAGE" ) VALUES ( ... ) so a column named "id" wouldn't be getting set.
If you want your entities to use a different case than the DB, and leave Postgres using lower case then I'd recommend using [Column] attributes to rename the columns:
public partial class ERRORLOG
{
[Key, Column(Name = "id")]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
[Column(Name = "message")]
public string MESSAGE { get; set; }
}
The other detail is that Order on the Column attribute is only needed when dealing with composite keys, such as many-to-many joining tables where the PK is made up of two or more columns. It isn't needed for normal single-value PKs.
If that isn't the cause, checking the insert statement in PGAdmin should give you a clue what NPGSQL / EF is attempting to execute.
I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}
I am aware of this, which states that it is not possible to create a primary key with non clustered index via code first. Is this still the case?
Ideally, I would like to specify via EntityTypeConfiguration, that my primary key (Guid) has a non-clustered index and there is another column (int) with a clustered index.
AFAIK this is not possible with EntityTypeConfiguration. However you can do this with Code-First migrations. Working example:
public class Product
{
public Guid Id
{ get; set; }
public int Price
{ get; set; }
}
class AppDbContext : DbContext
{
public DbSet<Product> Products
{ get; set; }
}
public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Products",
c => new
{
Id = c.Guid(nullable: false),
Price = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id, clustered: false)
.Index(t => t.Price, clustered: true);
}
public override void Down()
{
DropIndex("dbo.Products", new[] { "Price" });
DropTable("dbo.Products");
}
}
Result:
CREATE TABLE [dbo].[Products] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Price] INT NOT NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY NONCLUSTERED ([Id] ASC)
);
GO
CREATE CLUSTERED INDEX [IX_Price]
ON [dbo].[Products]([Price] ASC);
You can also do this with your OnModelCreating method like so:
modelBuilder.Entity(entityTypeName)
.HasKey(nameof(ClassName.Id))
.ForSqlServerIsClustered(false);
I am using code first migrations with an existing database. I used the reverse engineering feature to generate all the initial entities. However, the database was not well designed and one of the tables did not have a primary key designated. Apparently Entity Framework did its best to infer the primary key and didn't get it right. It looks like it decided to make two fields a compound primary key.
I want to actually remove one of the fields that it mistakenly thinks is part of the primary key and it's resulting in errors.
Here is the original schema of the table when the entity was created for it:
CREATE TABLE [dbo].[Table1](
[The_ID] [bigint] IDENTITY(1,1) NOT NULL,
[Field_1] [varbinary](max) NULL,
[Field_2] [bigint] NULL,
[Field_3] [varbinary](max) NULL,
[Field_4] [datetime] NULL,
[Field_5] [bit] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Table1] ADD CONSTRAINT [DF_Table1_F5] DEFAULT ((0)) FOR [Field_5]
GO
The_ID should have been designated as the primary key but as you can see it was not. Here is class the Visual Studio generated:
public partial class Table1
{
[Key]
[Column(Order = 0)]
public long The_ID { get; set; }
public byte[] Field_1 { get; set; }
public long? Field_2 { get; set; }
public byte[] Field_3 { get; set; }
public DateTime? Field_4 { get; set; }
[Key]
[Column(Order = 1)]
public bool Field_5 { get; set; }
}
It apparently decided to make The_ID and Field_5 into a compound primary key. At least that's how I'm interpreting this code. Now, I actually need to remove Field_5 from the table. I created an migration to do this, but since Entity Framework thinks its part of a primary key its doing weird things like dropping the primary key and re-adding it, which is resulting in errors. Here is the generated migration code:
public override void Up()
{
DropPrimaryKey("dbo.Table1");
AlterColumn("dbo.Table1", "The_ID", c => c.Long(nullable: false, identity: true));
AddPrimaryKey("dbo.Table1", "The_ID");
DropColumn("dbo.Table1", "Field_5");
}
Running this results in the following error:
ALTER TABLE [dbo].[Table1] DROP CONSTRAINT [PK_dbo.Table1]
System.Data.SqlClient.SqlException (0x80131904): 'PK_dbo.Table1' is not a constraint.
So how do I get myself out of this mess?
I tried removing the [Key] attributes from The_ID and Field_5 and creating a dummy migration using
Add-Migration dummy -IgnoreChanges
with the thought that I could then add the [Key] attribute back to The_ID and remove Field_5 but it won't let me create a migration unless at least one field is designated with the [Key] attribute. But if I do that to get the dummy migration in place then I can't do it in a real migration so I'm not able to actually designate The_ID as the primary key using code first migrations.
Any ideas?
If you have a Table without Primary Key in you Database EntityFramework will interpret every not nullable column in the table as part of the Primary Key.
In this case you could just remove the first two lines from the generated Migration, because there is no primary key to drop. EntityFramework just does not know about this fact.
public override void Up()
{
//DropPrimaryKey("dbo.Table1");
//AlterColumn("dbo.Table1", "The_ID", c => c.Long(nullable: false, identity: true));
AddPrimaryKey("dbo.Table1", "The_ID");
DropColumn("dbo.Table1", "Field_5");
}
I have a table which has an optional FK to another table and want to change that FK to a required relationship.
I have Automatic Migrations enabled and enabled destructive changes for this update. All entities in the database also have this key populated.
I changed this:
modelBuilder.Entity<Blog>().HasOptional(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
to:
modelBuilder.Entity<Blog>().HasRequired(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
and got the following error:
'FK_dbo.Blogs_dbo.Securables_AuthorSecurableId' is not a constraint.
Could not drop constraint. See previous errors.
There are no previous errors I could see (no inner exception ect.)
This post says you can get around this error with the following:
ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]
so i did:
public override void Up()
{
Sql("ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]");
DropForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables");
DropIndex("dbo.Blogs", new[] { "AuthorSecurableId" });
AlterColumn("dbo.Blogs", "AuthorSecurableId", c => c.Int(nullable: false));
AddForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables", "Id", cascadeDelete: true);
CreateIndex("dbo.Blogs", "AuthorSecurableId");
}
But still got the same error
EDIT:
the full code is avaliable here and a minimal models are below:
public class Blog
{
public int Id { get; set; }
public Securable AuthorSecurable { get; set; }
}
public class Securable
{
public int Id { get; set; }
}