Set EntityKey manually without accessing the database - c#

In my application (database-first) the primary keys are always created by client, not by the underlying database.
In Entity Framework v4 each Entity had it's EntityKey property that could be set manually.
However, I can't find it anymore in EF6?
Is there any specific reason why?
I don't want to call ctx.SaveChanges() only to have an Entity's key set as this means a call to the database.
I'm also wondering why ObjectContext.CreateEntityKey is gone?!

If You're using POCO's - here's a sample how it could be done
public class ChronosItem
{
[Column, Key, Required, MaxLength(26), DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
....
In case You cant adjust Your entities
override the "OnModelCreating" method on Your context.. like so..
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ChronosItem >()
.HasKey(p => p.Id)
.Property(p => p.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

Related

Entity Framework Core does not respect Identity columns

Entity Framework is not respecting my Identity columns. It insists on trying to insert a value into an Identity (auto-increment) column in my MS SQL DB, which is obviously an error since the DB is supposed to supply the value.
System.Data.SqlClient.SqlException: 'Cannot insert explicit value for identity column in table 'Assignee' when IDENTITY_INSERT is set to OFF.'
Why is it trying to do that? I've paired it down to a schema involving one table and one column:
CREATE TABLE [dbo].[Assignee](
[AssigneeID] INT IDENTITY(-1, 1) NOT NULL
CONSTRAINT [Assignee$PrimaryKey] PRIMARY KEY CLUSTERED
( [AssigneeID] ASC ))
After publishing this schema to my local DB I use Scaffold-DbContext to generate entity and context classes. The generated Assignee class contains just this public property.
public int AssigneeId { get; set; }
The context only refers to Assignee here:
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
Searching around I see people claiming that for E.F. to respect Identity columns, the context should configure the property with ValueGeneratedOnAdd(). In other words, the line in the context class should read:
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID")
.ValueGeneratedOnAdd();
I have two problems with this:
I'm starting with an existing DB and generating entity classes. If I need ValueGeneratedOnAdd() then why isn't Scaffold-DbContext generating it?
Even if I manually edit the generated context class and add ValueGeneratedOnAdd() it still doesn't work with the same error.
Elsewhere I see suggestions to use UseSqlServerIdentityColumn(). That also doesn't work for me. Points 1 and 2 still apply.
Any help would be greatly appreciate. Please don't suggest that I use IDENTITY_INSERT as that defeats the entire point of using auto-increment columns.
(I am using Entity Framework Core 2.2.3 and Microsoft SQL Server 14)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Todo>(entity =>
{
entity.Property(x => x.Id)
.HasColumnName("id")
.HasColumnType("int")
.ValueGeneratedOnAdd()
**.UseIdentityColumn();**
}
Try do this.
Ef Core Dependency : Microsoft.EntityFrameworkCore.SqlServer
This works for me:
modelBuilder.Entity<Assignee>().Property(e => e.AssigneeId).UseIdentityColumn();
So UseIdentityColumn() is the key.
I'm using Microsoft.EntityFrameworkCore.SqlServer v3.1.8.
Short version
We are getting and experiencing different results here one can reproduce the issue, others can not. My experience it depends on if the Id property's value is 0 or not.
Detailed version
My experience, that the default behavior (based on name convention) is definitely working, so in case you are naming your db entity's attribute (C# property) to Id or EntityNameId it should work. No C# entity class attributes neither OnModelCreating config is necessary.
The same time if the issue is there neither No C# entity class attributes neither OnModelCreating config will fix it.
...because if the Id property's value is not 0, the generated SQL will contain the explicit field name and value, so we got the error.
This is clearly and issue in EF core, but workaround is easy..
For DB first try adding [key] as a data annotation
With Data annotation
[Key]
public int AssigneeId { get; set; }
fluent API
modelBuilder.Entity<Assignee>()
.HasKey(o => o.AssigneeId);
See here or here if you want to use fluent API
I've tried to reproduce this issue based on your example but it appears to work just fine. I did not use Scaffold though, just coded class and I tried the model creating code you had and it hasn't had an issue. I suspect there has to be more to this though because with just the "Assignee" class, EF convention is expecting an "Assignees" table, so I suspect there is more mapping being set up.
Tested with EF Core 2.0.3 and 2.2.4
DB: used the OP's script.
Entity:
[Table("Assignee")]
public class Assignee
{
public int AssigneeId { get; set; }
}
I had to use the Table attribute to map to the table name.
Context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
}
as-per OP comment.
Test:
[Test]
public void TestIncrement()
{
using (var context = new TestDbContext())
{
var newItem = new Assignee();
context.Assignees.Add(newItem);
context.SaveChanges();
}
}
Works as expected.
However, what I'd normally have for the entity:
[Table("Assignee")]
public class Assignee
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("AssigneeID")]
public int AssigneeId { get; set; }
}
And then nothing for this column needed in the context OnModelCreating override.
I suspect that there is some additional configuration lurking somewhere given there is no mention of the table name issue, either manually added or via scaffold that is goofing up EF. I was full-on expecting EF to fail without the Key/DbGenerated attributes, but it seemed to work just fine.
Edit: Also tried this with scafolding running Scaffold-DbContext across the existing schema. Again, worked without an issue.
For comparison against your tests:
Generated DbContext: (Unaltered save removing the warning and connection string details.)
public partial class AssigneeContext : DbContext
{
public AssigneeContext()
{
}
public AssigneeContext(DbContextOptions<AssigneeContext> options)
: base(options)
{
}
public virtual DbSet<Assignee> Assignee { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Data Source=machine\\DEV;Initial Catalog=Spikes;uid=user;pwd=password;MultipleActiveResultSets=True");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
}
}
Generated Entity: (Unaltered)
public partial class Assignee
{
public int AssigneeId { get; set; }
}
I did figure out why my table annotation was needed. EF Core (Not sure if applies to EF6 as well) was basing the convention for the table name on the DbSet variable name in the DbContext. I couldn't see any config difference with the scaffold generated context and my own, except the DbSet name. I renamed my original DbContext's DbSet name to "Assignee" and it worked without the Table attribute.
That said, based on the information present your code should work. Something is lurking in the details because this example does work so you will need to provide more detail about an example that definitely doesn't work in your case.

Rebuilding Sql Server indexes name invalidate the hardcoded value in EF

In EF Context file,i have hard coded key name/index name in OnModelCreating. DBA rebuilt those indexes/keys with different names. Do i have to update those reference in code again ? or is there any other approach ?
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Customert>(entity =>
{
entity.HasKey(e => e.custId)
.HasName("PK__cust__4E739DAA");
}
}
As an option you can use Key attribute directly on entity property, for instance
using System.ComponentModel.DataAnnotations;
public class Customer
{
[Key]
public int CustomerId {get;set;}
}
documentation

Intermittent Issue With Entity Framework Creating Unwanted Rows

I have an MVC application that uses Entity Framework v6. We have a class
public class ChildObject
{
public string Name { get; set; }
....
}
that maps to a table in the database. This table has 6 rows that are never changed. Neither will there ever be any additions. We have a second class defined along the lines of the following:
public class ParentClass
{
public int ChildObjectId { get; set; }
public ChildObject ChildObject { get; set; }
....
}
Whenever a ParentClass object is created or updated the logic only references the ChildObjectId property. The ChildObject property is only referenced when data is pulled back for viewing. However about once per month an extra row appears in the ChildObject table that is a duplicate of an existing row. This obviously causes issues. However I can't see how this could happen seeing as we only ever save using the Id value. Any thoughts on how this could be occurring would be very much appreciated.
The typical culprit for behavior like you describe is when a new child entity is composed based on existing data and attached to the parent rather than the reference associated to the context. An example might be that you load child objects as a set to select from, and send the data to your view. The user wants to change an existing child reference to one of the 6 selections. The call back to the server passes a child object model where there is code something like:
parent.ChildObject = new ChildObject{ Name = model.Name, ... }
rather than:
var child = context.Children.Single(x => x.Id = model.ChildObjectId);
parent.ChildObject = child;
Depending on how your domain is set up you may run into scenarios where the EF context creates a new child entity when a navigation property is set. Check with a FindUsages on the ChildObject property and look for any use of the setter.
In general you should avoid combining the use of FK properties (ChildObjectId) with navigation properties (ChildObject) because you can get confusing behavior between what is set in the navigation reference vs. the FK. Entities should be defined with one or the other. (Though at this time EF Core requires both if Navigation properties are used.)
A couple notables from your example:
Mark the navigation property as virtual - This ensures that EF assigns a proxy and recognizes it.
Option A - Remove the FK child ID property. For the parent either use an EntityTypeConfiguration or initialize the DbContext to map the FK column:
EntityTypeConfiguration:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.Map(x => x.MapKey("ChildObjectId"));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().Map(x => x.MapKey("ChildObjectId")).WillCascadeOnDelete(false);
}
or Option B - Ensure the FK is linked to the reference, and take measures to ensure that the two are always kept in sync:
public class ParentClassConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentClassConfiguration()
{
ToTable("ParentTable");
HasKey(x => x.ParentObjectId)
.Property(x => x.ParentObjectId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.ChildObject)
.WithMany()
.HasForeignKey(x => x.ChildObjectId));
.WillCascadeOnDelete(false);
}
}
or on context model generation: (Inside your DbContext)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentObject>().HasRequired(x => x.ChildObject).WithMany().HasForeignKey(x => x.ChildObjectId)).WillCascadeOnDelete(false);
}
Option B is the only one currently available with EF Core to my knowledge, and it may help mitigate your issue but you still have to take care to avoid discrepancies between the navigation property and the FK. I definitely recommend option A, though it will likely require a bit of change if your code is commonly accessing the FK column.

Entity Framework 7: Identifying relationships

In previous versions of EF I can use following code to implement an identifying relationship:
public class Child
{
[Key, Column(Order = 1)]
public virtual int Id { get; set; }
[Key, Column(Order = 2)]
public virtual int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
It's needed to easily remove a child from collection like this:
var parent = _context.Parents.First();
var child = parent.Children.First();
parent.Children.Remove(child);
_context.SaveChanges();
This approach is described in http://www.kianryan.co.uk/2013/03/orphaned-child/ (the method #2).
But in EF7 this code throws exception when migration is creating:
An exception was thrown while executing a resolve operation. See the
InnerException for details. ---> Entity type 'Child' has composite
primary key defined with data annotations. To set composite primary
key, use fluent API.
I also tried to use FluentAPI as described in How to define nested Identifying Relationships Entity Framework code first in following code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
modelBuilder.Entity<Child>()
.HasKey(c => new {c.Id, c.ParentId});
base.OnModelCreating(modelBuilder);
}
This approach allow generate a migration successfully, but when I tried to remove a child from Children collection I got following exception:
System.InvalidOperationException: The association between entity types
'Parent' and 'Child' has been severed but the foreign key for this
relationship cannot be set to null. If the dependent entity should be
deleted, then setup the relationship to use cascade deletes.
But I wouldn't like to use cascade deletes, I would like to use identifying relationship!
Please, help me understand what I do incorrect. Thank you!
Use cascade on delete instead as this is what it's used for:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
.WillCascadeOnDelete(true);
https://msdn.microsoft.com/en-gb/data/jj591620.aspx
Just in case someone sees this error, let me tell you how I resolved mine:
When you do an update, on EF you need to first query the database and get the data model, then map the Domain layer model with your changes onto it (basically copying fields onto the data), and finally call the DBContext update method, then save changes.
My problem was that my model (not the data model, the domain model) also had the sub objects on it.
So here's the data layer model (for example):
public class Parent
{
public int ChildId {get; set; }
[ForeignKey("ChildId")]
public virtual Child Child { get; set; }
}
And here's how the domain layer model should be:
public class Parent
{
public int ChildId { get; set; }
//public Child Child { get; set; } // this caused the error, keep reading if you want to know more.
}
When I was seeing the error, I had been using Autofac's runtime mapper to map the domain layer model's properties onto the data layer model. However, the child in the domain layer model was null, so it would nullify the data layer, causing the error:
"The association between entity types 'Parent' and 'Child' has been severed but the foreign key for this relationship cannot be set to null. If the dependent entity should be deleted, then setup the relationship to use cascade deletes."
By the way, in the db context class, I have the following relationship defined:
modelBuilder.Entity<Parent>()
.HasOne(a => a.Child)
.WithMany()
.HasForeignKey(p => p.ChildId)
.OnDelete(DeleteBehavior.Restrict);
It's working.

Entity Framework, Foreign key constraint may cause cycles or multiple cascade paths

I use Entity Code first for my project. Basically I have 3 class Users,Branchs and UsersBranchs.
Users contains UserID, Name ,...
Branchs contains BranchID, Location, ... and UserID which is refer to creator of branch
and UsersBranchs just have have two column BranchID and UserID which is define which user is in which branch
the problem is I get this error:
'FK_dbo.UsersBranchs_dbo.Users_UsersID' on table 'UsersBranchs' may
cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or
ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Can you help me please?
Update
It's UsersBranchs Class
[ForeignKey("UserID")]
public CoreUsers User { get; set; }
public Guid UsersID { get; set; }
[ForeignKey("BranchID")]
public Branchs Branch { get; set; }
public Guid BranchID { get; set; }
And also add this line to DbContext class to use both UserID and BranchID as key
modelBuilder.Entity<UsersBranchs>().HasKey(x => new { x.UserID, x.BranchID });
Branchs Class is
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ID { get; set; }
[ForeignKey("UserID")]
public CoreUsers User { get; set; }
public Guid UserID { get; set; }
public .....
Users Class is
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ID { get; set; }
public .....
Not being able to handle multiple cascade paths and cascade delete to same table has been a limitation of Sql Server for a long time. Just Google the error message. Basically, if you want to use cascade delete then you'll have to make sure that there is only a single cascade path.
At the moment you have two paths from Branchs -> UsersBranchs and Branchs -> Users -> UsersBranchs.
EF by default sets cascade delete but it may be stopped by removing the conventions in your DbContext.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Manually set cascade delete behaviour
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
Then you'll have to set WillCascadeOnDelete(true) on any relationships where you want cascade delete. See the Entity Framework documentation.
Other than that your model seems a bit strange. You look like you're trying to make a many-to-many link/join table, UsersBranchs, but you also have a single User on the Branchs which doesn't really make sense. Do you even need the UsersBranchs table in that case? Did you mean to have a collection of Users on your Branchs, i.e. a navigation property rather than a foreign key, which gives a one-to-many relationship Branchs -> Users?
As an aside I really dislike using plurals for single entities.
I think you are getting the problem because you didn't tell Entity framework how it will treat these classes on delete on cascade
in your DbContext class, override the OnModelCreating method and write this code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserBranch>()
.HasRequired(t => t.CoreUsers)
.WithMany()
.HasForeignKey(t => t.UserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserBranch>()
.HasRequired(t => t.Branch)
.WithMany()
.HasForeignKey(t => t.BranchID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Branch>()
.HasRequired(t => t.User)
.WithMany()
.HasForeignKey(t => t.UserID)
.WillCascadeOnDelete(false);
}
hope this will help you

Categories