Entity framework computed primary key issue - entity-framework-core

I have issue with computed primary key.
In table class I have this property. It is primary computed key.
[Key]
[StringLength(24)]
public string TagId { get; set; } = null!;
In context class I have
entity.Property(e => e.TagId).HasComputedColumnSql("(CONVERT([nvarchar](24),([TagCd]+'.')+case when len([Nbr])=(1) then '0'+CONVERT([nvarchar](4),[Nbr]) else CONVERT([nvarchar](4),[Nbr]) end))", true);
In OnModelCreating method
modelBuilder.Entity<Tag>(entity =>
{
entity.Property(e => e.TagId).ValueGeneratedOnAdd();
});
When I try to save new record entity framewrok still add primary key to insert query and then I get exception
SqlException: The column TagId cannot be modified because it is either a computed column or is the result of a UNION operator
Can someone help me ? What should I do ? It is database first approach.

Related

Entity Framework - error when adding entity with related entity

I have two entities:
public class EntityA
{
public int? Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
public class EntityB
{
public int? Id { get; set; }
public string Version { get; set; }
}
I have existing records for EntityB already in the database. I want to add a new EntityA with reference to one of the EntityB records.
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
var entityA = new EntityA { Name = "Test", EntityB = entityB };
_dbContext.Add(entityA);
_dbContext.SaveChanges();
When the above code runs I get the following error:
System.InvalidOperationException: The property 'Id' on entity type 'EntityB' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
This seems to me, that the save is trying to also add EntityB, not just a reference to it. I do have the relationship specified in the database as well as in Entity Framework, e.g. when querying for EntityA if I include EntityB in the select, I get the referenced entity as well (so the relationship works).
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
modelBuilder.Entity<EntityB>(e =>
{
e.HasKey(p => p.Id);
}
How can I save a new EntityA, with only a reference to the selected EntityB, rather than saving both entities?
It looks like you are trying to Extend EntityB with an optional 1:1 reference to a Row n the new table EntityA. You want both records to have the same value for Id.
This 1:1 link is sometimes referred to as Table Splitting.
Logically in your application the record from EntityB and EntityA represent the same business domain object.
If you were simply trying to create a regular 1 : many relationship, then you should remove the HasOne().WithOne() as this creates a 1:1, you would also not try to make the FK back to the Id property.
The following advice only applies to configure 1:1 relationship
you might use Table Splitting for performance reasons (usually middle tier performance) or security reasons. But it also comes up when we need to extend a legacy schema with new metadata and there is code that we cannot control that would have broken if we just added the extra fields to the existing table.
Your setup for this is mostly correct, except that EntityA.Id cannot be nullable, as the primary key it must have a value.
public class EntityA
{
public int Id { get; set; }
public string Name { get; set; }
public EntityB { get; set; }
}
If you want records to exist in EntityA that DO NOT have a corresponding record in EntityB then you need to use another Id column as either the primary key for EntityA or the foreign key to EntityB
You then need to close the gap with the EntityA.Id field by disabling the auto generated behaviour so that it assumes the Id value from EntityB:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne()
.HasForeignKey<EntityB>(p => p.Id);
}
I would probably go one step further and add the Reciprocating or Inverse navigation property into EntityB this would allow us to use more fluent style assignment, instead of using _dbContext.Add() to add the record to the database:
public class EntityB
{
public int Id { get; set; }
public string Version { get; set; }
public virtual EntityA { get; set; }
}
With config:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id).ValueGeneratedNever();
e.HasOne(p => p.EntityB)
.WithOne(p => p.EntityA)
.HasForeignKey<EntityB>(p => p.Id);
}
This allows you to add in a more fluent style:
var entityB = _dbContext.EntityB.FirstOrDefault(e => e.Id == 1);
entityB.EntityA = new EntityA { Name = "Test" };
_dbContext.SaveChanges();
This will trip up because you are using EntityA's PK as the FK to Entity B, which enforces a 1 to 1 direct relation. An example of this would be to have something like an Order and OrderDetails which contains additional details about a specific order. Both would use "OrderId" as their PK and OrderDetails uses it's PK to relate back to its Order.
If instead, EntityB is more like an OrderType reference, you wouldn't use a HasOne / WithOne relationship because that would require Order #1 to only be associated with OrderType #1. If you tried linking OrderType #2 to Order #1, EF would be trying to replace the PK on OrderType, which is illegal.
Typically the relationship between EntityA and EntityB would require an EntityBId column on the EntityA table to serve as the FK. This can be a property in the EntityA entity, or left as a Shadow Property (Recommended where EntityA will have an EntityB navigation property) Using the above example with Order and OrderType, an Order record would have an OrderId (PK) and an OrderTypeId (FK) to the type of order it is associated with.
The mapping for this would be: (Shadow Property)
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey("EntityBId");
}
An OrderType can be assigned to many Orders, but we don't have an Orders collection on OrderType. We use the .HasForeignKey("EntityBId") to set up the shadow property of "EntityBId" on our EntityA table. Alternatively, if we declare the EntityBId property on our EntityA:
modelBuilder.Entity<EntityA>(e =>
{
e.HasKey(p => p.Id);
e.HasOne(p => p.EntityB)
.WithMany()
.HasForeignKey(p => p.EntityBId);
}
On a side note, navigation properties should be declared virtual. Even if you don't want to rely on lazy loading (recommended) it helps ensure the EF proxies for change tracking will be fully supported, and lazy loading is generally a better condition to be in at runtime than throwing NullReferenceExceptions.

How to delete row with 1:1 relation to the same table?

I using Entity Framework Core, and I have a table:
public class BlogComment
{
public int Id { get; set; }
public BlogPost Post { get; set; }
[StringLength(100)]
public string AuthorName { get; set; }
[StringLength(254)]
public string AuthorEmail { get; set; }
public bool SendMailOnReply { get; set; }
[StringLength(2000)]
public string Content { get; set; }
public DateTime CreatedTime { get; set; }
public int? ReplyToId { get; set; }
public BlogComment ReplyTo { get; set; }
}
From this, EFC generates the following table:
CREATE TABLE [dbo].[BlogComment] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AuthorEmail] NVARCHAR (254) NULL,
[AuthorName] NVARCHAR (100) NULL,
[Content] NVARCHAR (2000) NULL,
[CreatedTime] DATETIME2 (7) NOT NULL,
[PostId] INT NULL,
[ReplyToId] INT NULL,
[SendMailOnReply] BIT NOT NULL,
CONSTRAINT [PK_BlogComment] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_BlogComment_BlogPost_PostId] FOREIGN KEY ([PostId]) REFERENCES [dbo].[BlogPost] ([Id]),
CONSTRAINT [FK_BlogComment_BlogComment_ReplyToId] FOREIGN KEY ([ReplyToId]) REFERENCES [dbo].[BlogComment] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_BlogComment_PostId]
ON [dbo].[BlogComment]([PostId] ASC);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_BlogComment_ReplyToId]
ON [dbo].[BlogComment]([ReplyToId] ASC) WHERE ([ReplyToId] IS NOT NULL);
Some comments are send as a reply to another, but not all. When the original comment is deleted, the reply becomes a normal comment. So, following this tutorial, the configuration looks is this:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithOne()
.HasForeignKey<BlogComment>(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
The delete method is pretty simple:
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
context.BlogComment.Remove(comment);
await context.SaveChangesAsync();
But I can't run it, I get an error:
System.Data.SqlClient.SqlException: The DELETE statement conflicted with the SAME TABLE REFERENCE constraint "FK_BlogComment_BlogComment_ReplyToId".
How can I fix this?
To wrap up the conversation in the comments:
First, the self reference is a 1:n association:
modelBuilder.Entity<BlogComment>()
.HasOne(p => p.ReplyTo)
.WithMany(c => c.Replies)
.HasForeignKey(c => c.ReplyToId)
.IsRequired(false)
.OnDelete(<we'll get to that>);
So, just for convenience, BlogComment now also has a property
public ICollection<BlogComment> Replies { get; set; }
However, I can't create the table using
.OnDelete(DeleteBehavior.SetNull);
It gives me
Introducing FOREIGN KEY constraint 'FK_BlogComments_BlogComments_ReplyToId' on table 'BlogComments' may cause cycles or multiple cascade paths.
This is a Sql Server restriction we just have to accept, no way to evade it. The only way to get the desired cascade behavior is
.OnDelete(DeleteBehavior.ClientSetNull);
Which is:
For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are set to null. This helps keep the graph of entities in a consistent state while they are being tracked, such that a fully consistent graph can then be written to the database. (...) This is the default for optional relationships.
I.e.: the client executes SQL to nullify the foreign key values. The child records should be tracked though. To remove a BlogComment parent the delete action should look like:
using (var db = new MyContext(connectionString))
{
var c1 = db.BlogComments
.Include(c => c.Replies) // Children should be included
.SingleOrDefault(c => c.Id == 1);
db.BlogComments.Remove(c1);
db.SaveChanges();
}
As you see, you don't have to set ReplyToId = null, that's something EF takes care of.
For me, I had to Include() the entities I needed to be "dealt with" when I deleted an entity. EF cant manage things it is not currently tracking.
var breedToDelete = context.Breed
.Include(x => x.Cats)
.Single(x => x.Id == testBreedId);
context.Breed.Remove(breedToDelete);
context.SaveChanges();
I could get it working by manually setting ReplyTo to null. I'm still looking for a better solution, or an explanation why is it needed. Isn't it what OnDelete(DeleteBehavior.SetNull) supposed to do?
var comment = await context.BlogComment.Include(c => c.ReplyTo).SingleAsync(m => m.Id == id);
var reply = await context.BlogComment.SingleOrDefaultAsync(m => m.ReplyToId == id);
if (reply != null)
{
reply.ReplyTo = null;
reply.ReplyToId = null;
context.Entry(reply).State = EntityState.Modified;
}
context.BlogComment.Remove(comment);

The INSERT statement conflicted with the FOREIGN KEY constraint . The statement has been terminated

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_T_OrderDetails_T_SelectionList". The conflict occurred in database "AlbumPortal", table "dbo.T_SelectionList", column 'ID'.
I am new to ASP.NET MVC with EF code-first approach, I get this error when trying to save data to T_OrderDetails from controller.
In T_OrderDetails table 'SelectionList_ID' is foreign key from the table dbo.T_SelectionList. For that it is defined as
public virtual T_SelectionList T_SelectionList { get; set; }
in 'T_OrderDetails' and
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<T_OrderDetails> T_OrderDetails { get; set; }
in 'T_SelectionList'. Also
modelBuilder.Entity<T_SelectionList>()
.HasMany(e => e.T_OrderDetails)
.WithOptional(e => e.T_SelectionList)
.HasForeignKey(e => e.SelectionList_ID);
is defined. Then I don't understand why this error getting.

How can I define in a DB first POCO model, a field that is both PK and FK together

Table EMPLOYEE has MST_SQ (master-sequence) as both it's primary key, and as an FK to the primary key of table MASTER, which is also named MST_SQ. This table is used to join several other tables as well so that they all have the same PK. That is as far as my understanding goes.
I need to defined a 1 to 1 relationship in my model between class Employee and class Master, but I simply cannot find a way to do this. It seems only relationships with multiplicty allow an FK field to be speficied, and those that look like for 1 to 1, e.g. has optional(...)..WithRequiredPrincipal(....) has no FK space.
I could do some manual coding to link EMPLOYEE and MASTER when the are loaded, but how could I tell they were loaded. Is there any event that signals a POCO being populated from the DB? Or, the real question, how do I define this relationship in code?
From Relationships and Navigation Properties :
When working with 1-to-1 or 1-to-0..1 relationships, there is no
separate foreign key column, the primary key property acts as the
foreign key
From Configuring a Required-to-Optional Relationship (One-to–Zero-or-One) :
because the name of the property does not follow the convention the
HasKey method is used to configure the primary key
public class Master
{
public int MST_SQ { get; set; }
public virtual Employee Employee { get; set; }
}
public class Employee
{
public int MST_SQ { get; set; }
public virtual Master Master { get; set; }
}
The Employee has the MST_SQ property that is a primary key and a foreign key:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Master>().HasKey(m => m.MST_SQ);
modelBuilder.Entity<Employee>().HasKey(e => e.MST_SQ);
modelBuilder.Entity<Employee>()
.HasRequired(e => e.Master) //Employee is the Dependent and gets the FK
.WithOptional(m => m.Employee); //Master is the Principal
}
Generated migration code:
CreateTable(
"dbo.Employees",
c => new
{
MST_SQ = c.Int(nullable: false),
})
.PrimaryKey(t => t.MST_SQ)
.ForeignKey("dbo.Masters", t => t.MST_SQ)
.Index(t => t.MST_SQ);
CreateTable(
"dbo.Masters",
c => new
{
MST_SQ = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.MST_SQ);
So you don't need the "FK space" because EF makes it the foreign key without you having to specify it

EF 4.1 code first: how to refer to child records from parent in a one-to-many relationship

I have a simple 1:many aggregate relationship, lets say:
public class Parent
{
public string Name {get; set;}
public Child SelectedChild {get; set;}
public Child PublishedChild {get; set;}
public virtual ICollection<Child> AllChildren {get; set;}
}
public class Child
{
public string Name {get; set;}
[Required]
public Parent Father {get; set;}
}
When creating the schema from this model I get the error:
Introducing FOREIGN KEY constraint 'Parent_SelectedChild' on table 'Parent' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints
So I add the following to OnModelCreating:
modelBuilder.Entity<Child>()
.HasRequired(v => v.Parent)
.WithOptional(c => c.SelectedChild)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Child>()
.HasRequired(v => v.Parent)
.WithOptional(c => c.PublishedChild)
.WillCascadeOnDelete(false);
This gets round the original error but I now get:
Unable to determine the principal end of the 'xxx.Parent_SelectedChild' relationship.
Multiple added entities may have the same primary key.
Can anyone help?
All I essentially want to do is refer to particular child records on a 1:many aggregate relationship from the parent. I assume EF will create INT child id columns on the parent called e.g. SelectedChild_Id & PublishedChild_Id (or similar).
Thanks in advance
-macon
Edit: In response to #Slauma:
I can get a schema generated using:
modelBuilder.Entity<Parent>()
.HasOptional(p => p.SelectedChild)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Parent>()
.HasOptional(p => p.PublishedChild)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Parent>()
.HasMany(p => p.AllChildren)
.WithRequired(c => c.Father)
.WillCascadeOnDelete(false);
But this generates multiple FK on the Child record e.g. Parent_Id, Parent_Id1. I just want a reference from the Parent to one of the child rows e.g. Parent_SelectedChildId. Do I have to do this manually with an int column on parent?
I think you have three 1-to-many relationships:
modelBuilder.Entity<Parent>()
.HasOptional(p => p.SelectedChild)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Parent>()
.HasOptional(p => p.PublishedChild)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Parent>()
.HasMany(p => p.AllChildren)
.WithRequired(c => c.Father)
.WillCascadeOnDelete(false);
Edit
I've tested my mapping above with exactly the Parent and Child class you provided in your question - with the only exception that I have added a primary key property to both classes: public int Id { get; set; }. Otherwise EF would complain about a missing key property. This mapping doesn't throw an exception and creates the following tables in the database:
Parents table:
- Id int not nullable (PK)
- Name nvarchar(MAX) nullable
- SelectedChild_Id int nullable (FK)
- PublishedChild_Id int nullable (FK)
Children table:
- Id int not nullable (PK)
- Name nvarchar(MAX) nullable
- Father_Id int not nullable (FK)
So, there are the three foreign key columns as expected.
Since you get an exception according to your comment, I guess that there is actually some important difference in the code you have tested.
BTW: Mapping the two navigation properties of the Parent class as One-to-One relationships is much more difficult, if not impossible. In EF you need a shared primary key between the two tables to map a One-to-One relationship, so it would not be possible to assign two different entities to the two navigation properties because they cannot both have the same key as the parent.