Entity Framework 5 and Code First - Can I get it to do a Rename instead of Drop and Create? - entity-framework

I'm using EF 5 Code First. I have the following Fluent API code that sets a navigation property for 'SaleZipCode'.
private void MyTable(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyType>()
.HasRequired(a => a.SaleZipCode)
.WithMany()
.Map(map => map.MapKey("SaleZipCodeId"));
}
I realize I misnamed 'SaleZipCode' and it should be 'ZipCode'. However, when I do this, EF attempts to drop the SaleZipCode column and add a ZipCode column when I do an Update-Database, instead of just doing a rename. This doesn't work because I have existing data in the table. Is there a way I can get EF to do a rename and not a drop and recreate?

Renaming property in your entity should not affect database, because there is no SaleZipCode column in database. Foreign key is mapped to SaleZipCodeId column, and only changing this foreign key column mapping will affect database. E.g. if you will change mapping to
modelBuilder.Entity<MyType>()
.HasRequired(a => a.Foo) // changing this will not affect database
.WithMany()
.Map(map => map.MapKey("ZipCodeId")); // column name changed
Then following migration will be generated
public override void Up()
{
RenameColumn(table: "dbo.MyTypes",
name: "SaleZipCodeId", newName: "ZipCodeId");
}
public override void Down()
{
RenameColumn(table: "dbo.MyTypes",
name: "ZipCodeId", newName: "SaleZipCodeId");
}
Simple renaming columns, data will be preserved.

Related

IDENTITY_INSERT ON error with foreign key

I'm trying to set Id manually for some entries.
I have a Customer class with an Identity field
modelBuilder.Entity<Customer>().Property(f => f.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
and in the insert part I have
using (var transaction = ctx.Database.BeginTransaction())
{
ctx.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[Customers] ON");
ctx.Customers.AddRange(customersList);
ctx.SaveChanges();
ctx.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[Customers] OFF");
transaction.Commit();
}
but I'm still getting the exception
Explicit value must be specified for identity column in table
'Customers' either when IDENTITY_INSERT is set to ON or when a
replication user is inserting into a NOT FOR REPLICATION identity
column.
In the StateEntries of my exception, I can see that all the fields that are stored in another table like "Contacts" seems to be the cause
customer.Contacts = new List<Contact> {new Contact {Name = "Test", … }}
How can I make it work?
That error message is misleading. You are succeeding in turning IDENTITY_INSERT on, but EF is still generating an INSERT statement under the assumption that the key value will be generated by the IDENTITY column.
So you additionally must generate an INSERT statement containing the key value. You can, of course, use .ExecuteSqlCommand() to perform the INSERT as well. Or you can get EF to do it, but you must use a model where that Entity does not have database-generated keys. OnModelCreating configures the model only once per ModelType, so you can't just put a switch in there.
Instead you can create a subtype of your DbContext for seeding, overriding the entity configuration of the base DbContext. Like this:
class Db_Seed: Db
{
public Db_Seed(string constr) : base(constr)
{
Database.SetInitializer<Db_Seed>(null); //no initializer
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Customer>().Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
You still must set IDENTITY_INSERT on (since the table does have an IDENTITY), but with this context EF will not generate an INSERT statement that assumes the server will generate the key.

EF6: navigation property foreign key to foreign key

I have a legacy database which I cannot change with two entities with a foreign key property in each entity:
EntityOne
ForeignKey1 (pointing to EntityTwo.ForeignKey2)
EntityTwo
ForeignKey2 (pointing to EntityOne.ForeignKey1)
These foreign keys point to eachother. How can I map this relation with EF6? If it isn't possible, is there any workaround?
I tested with a test database which actually has a foreign key relation from EntityOne to EntityTwo and another from EntityTwo to EntityOne and auto generating the model, with code first from database, did not create any relationships between the two tables:
public partial class EntityFrameworkTesting : DbContext
{
public EntityFrameworkTesting()
: base("name=EntityFrameworkTesting")
{
}
public virtual DbSet<EntityOne> EntityOne { get; set; }
public virtual DbSet<EntityTwo> EntityTwo { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
With some additional trying I've succeeded to build a relationship between these entities. However, I had to delete the foreign key properties from my POCO classes to allow usage of the MapKey function. I would still like to access the foreign key, but when adding the foreign key property again, I receive an error stating: "ForeignKey2: Name: Each property name in a type must be unique. Property name 'ForeignKey2' is already defined.".
Basically I'm looking for the relationship functionality I have now, but with added foreign key property on my POCO class.
modelBuilder.Entity<EntityTwo>()
.HasRequired(entity => entity.EntityOne)
.WithRequiredDependent(entity => entity.EntityTwo)
.Map(m => m.MapKey("ForeignKey2"));
In this situation you want to use a shared primary key, meaning the PK of the dependent entity is also the FK to the principal entity (and references the principal entity's PK), enforcing a 1..0 relationship. You can view this answer to see how it's configured.
If you can't change your database schema, you're out of luck in EF6. EF6 does not support FK's referencing any candidate key other than the primary key.
EFCore supports FK's referencing alternate candidate keys, but still won't create two mutually referencing FK's - only the FK in the dependent table will be created (or so it seems via my limited testing just now). You can do so via the following FluentAPI configuration (assuming EntityOne is the principal entity):
modelBuilder.Entity<EntityOne>()
.HasAlternateKey( eo => eo.ForeignKey1 );
modelBuilder.Entity<EntityTwo>()
.HasOne( et => et.EntityOne )
.WithOne( eo => eo.EntityTwo )
.IsRequired()
.HasForeignKey<EntityTwo>( et => et.ForeignKey2 )
.HasPrincipalKey<EntityOne>( eo => eo.ForeignKey1 );
EntityOne.ForeignKey1 should be nullable. When testing, I had to manually specify the type parameters for the Has*Key methods, not sure why.

How to declare cascade delete in EF when child table isn't mapped as separate entity

I have got a database first approach with EF5 and here is a fragment of mappings:
internal class xxxMapping : EntityTypeConfiguration<Order>
{
public xxxMapping ()
{
ToTable("my_table");
//......
HasMany(it => it.Documents)
.WithMany()
.Map(
m =>
{
m.ToTable("dependent_table");
m.MapLeftKey("left_key_id");
m.MapRightKey("right_key_id");
});
}
What is the best way to declare, using fluent API, that when some row is deleted from my_table, then dependent rows from dependent_table will be deleted too (Cascade delete option in FK)
UPD It seems to be working without any additional code (Of course - if foreign key in table is configured properly). But i'm not sure it's a good practice to do so

How do I tell Entity (Code First) to not send the Key ID field to the database?

My code:
Models.Resource r = new Models.Resource();
r.Name = txtName.Text;
r.ResourceType = resTypes.Find(rt => rt.Name == "Content");
r.ResourceContents.Add(_resourceContent.Find(rc => rc.ID == _resourceContentID));
ctx.Resource.Add(r);
ctx.SaveChanges();
ctx.SaveChanges() causes the error:
Cannot insert explicit value for identity column in table 'Resources' when IDENTITY_INSERT is set to OFF.
Looking at what's being sent to SQL:
ADO.NET:Execute NonQuery "INSERT [dbo].[Resources]([ID], [Name], [Description], [IsOnFile],
[ContentOwnerAlias], [ContentOwnerGroup], [ResourceTypes_ID])
VALUES (#0, #1, #2, #3, #4, #5, NULL)"
My POCO Resource has ID as a Key:
public partial class Resource
{
public Resource()
{
}
[Key]
public int ID { get; set; }
And my Map code:
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
How do I tell Entity to not send the Key ID field to the database?
If your PK is generated by the database (like an identity) you have to configure it in your Map.
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
You do not need the HasKey(t => t.ID) Fluent API mapping or the [Key] Data Attribute because by convention EF will assume that an integer field named ID is the key and is database generated.
As an aside, I'd recommend that when you are not following conventions you should choose one method or the other - otherwise you are repeating yourself and when you want to change something you need to change it in 2 places.
I'm not sure why the field in the database isn't already database generated - maybe when you define the field via the fluent api you have to specify that too. What I do know is that in order to make EF change a key field to be database generated you will need to drop the table.
So - rollback the migration or drop the table / database, then remove the data attribute, remove the fluent mapping and recreate.
This issue is currently on a "backlog" in the entity framework. If you want to vote for it you can do that here: Migrations: does not detect changes to DatabaseGeneratedOption
Other References:
Identity problem in EF
Switching Identity On/Off With A Custom Migration Operation

How to modify mapping name in many-to-many assossiation

When I create an many to many assossiation on two entities, there will be a relationship table created contains the two entities' primary key in the database. But the name of the column seems no sence, how to modify them?
eg.
an Article table and a Blog table, the relationship table will contains two columns' like Article_ArticleID and Blog_BlogID
I want to modify the name at mapping window, but I find it read only, anyone can help?
Thanks in advance!
you can use:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(b => b.Articles)
.WithMany(a => a.Blogs)
.Map
(
m =>
{
m.MapLeftKey("BlogID");
m.MapRightKey("AritcleID");
m.ToTable("BlogArticle");
}
);
base.OnModelCreating(modelBuilder);
}