Error when running Seed method using code first EF 6 - entity-framework

I was trying to run the update-database command to insert some initial data in my database and I got and error. Here is my class:
[Table("NotAllowedDomain")]
public class NotAllowedDomain : Entity
{
[Key]
public int DomainId { get; set; }
public string Domain { get; set; }
}
and here is my Seed method:
protected override void Seed(DbContext context)
{
var notAllowedDomainsList = new List<NotAllowedDomain>
{
new NotAllowedDomain {Domain = "test.com"},
new NotAllowedDomain {Domain = "test1.com"},
new NotAllowedDomain {Domain = "test2.co"}
};
notAllowedDomainsList.ForEach(x => context.NotAllowedDomains.AddOrUpdate(n => n.Domain, x));
}
After the Add-Migration AddingNotAllowedDomains, I ran Update-Database and got this error:
Running Seed method.
System.InvalidOperationException: Saving or accepting changes failed because more than one entity of type 'Model.NotAllowedDomain' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
at System.Data.Entity.Core.Objects.ObjectStateManager.FixupKey(EntityEntry entry)
at System.Data.Entity.Core.Objects.EntityEntry.AcceptChanges()
at System.Data.Entity.Core.Objects.EntityEntry.ChangeObjectState(EntityState requestedState)
at System.Data.Entity.Core.Objects.EntityEntry.ChangeState(EntityState state)
at System.Data.Entity.Internal.StateEntryAdapter.ChangeState(EntityState state)
at System.Data.Entity.Internal.InternalEntityEntry.set_State(EntityState value)
at System.Data.Entity.Infrastructure.DbEntityEntry.set_State(EntityState value)
at Repository.Pattern.Ef6.DataContext.SyncObjectsStatePreCommit()
at Repository.Pattern.Ef6.DataContext.SaveChanges()
at System.Data.Entity.Migrations.DbMigrator.SeedDatabase()
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.SeedDatabase()
at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.<Update>b__b()
at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.Run()
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.<.ctor>b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
Saving or accepting changes failed because more than one entity of type 'Model.NotAllowedDomain' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
Any help?

Do you have an EntityTypeConfiguration class for NotAllowedDomain?
public class NotAllowedDomainConfiguration : EntityTypeConfiguration<NotAllowedDomain>
{
public NotAllowedDomainConfiguration()
{
//Table
ToTable("NotAllowedDomains");
//Primary key
HasKey(e => e.DomainId);
//Properties
Property(e => e.DomainId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(e => e.Domain).HasMaxLength(100).IsRequired();
}
}
Reference this in your DbContext class by overriding the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new NotAllowedDomainConfiguration());
base.OnModelCreating(modelBuilder);
}

Related

EF Core - duplicate entry in table index when adding record to table with composite keys

I have a class with a composite key. When I try to save a record where a part of that key is contained in another record's key, I am getting an exception although the composite keys as a whole are unique.
Data type with navigation:
public class ProxyInfo
{
[Key, Column(Order = 0, TypeName = "varchar")]
[StringLength(80)]
public string AccountID { get; set; } = string.Empty;
[Key, Column(Order = 1, TypeName = "varchar")]
[StringLength(80)]
public string ProxyID { get; set; } = string.Empty;
[ForeignKey(nameof(ProxyID))]
public virtual UserInfo? Proxy { get; set; }
// a few more properties
}
OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProxyInfo>()
.HasKey(e => new { e.AccountID, e.ProxyID })
.HasName("PK_ProxyInfo");
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
// more code follows
}
Here's the relevant controller code:
[Route(Routes.Base + "[controller]")]
[ApiController]
public class ProxyInfoApiController : ControllerBase
{
private readonly DS2DbContext _context;
public ProxyInfoApiController(DS2DbContext context)
{
_context = context;
}
[HttpPost("Add")]
[AllowAnonymous]
public async Task<IActionResult> Add([FromBody] ProxyInfo proxyInfo)
{
try
{
_context.ProxyInfo.Add(proxyInfo);
await _context.SaveChangesAsync();
}
catch (Exception e)
{
ServerGlobals.Logger?.Error($"ProxyInfoController.Add: Error '{e.Message}' occurred.");
}
return Ok(); // Created(proxyInfo.ID.ToString(), proxyInfo);
}
}
The error message reads:
A row with a duplicate key cannot be inserted in the dbo.ProxyInfo
object with the unique IX_ProxyInfo_ProxyID-Index. The duplicate
key value is "X".
The complex key I tried to insert was {"B","X"}. The only other record in the ProxyInfo table has key {"A", "X"}; So there should be two different ProxyInfo records referencing the same UserInfo record.
The problem seems to be that an index is being tried to be updated with a value it already contains. However, the indices of both records can be identical, as multiple ProxyInfos can reference the same UserInfo. So actually, no duplicate entry is created. It's just that a 2nd ProxyInfo record uses the same user as the 1st one.
I just found out that the relevant index is created during initial migration with a unique:true attribute. The question is whether I can make EF Core skip updating the index when it already contains an index that it is trying to add again.
I found the problem. It was this statement:
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
which should have been
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithMany()
.OnDelete(DeleteBehavior.NoAction);
What I also didn't know is that when I make changes like this one, I need to create and execute a migration. Once I did that, the index got changed into a non unique one.
I am only doing EF Core/Blazor for a year and I am dealing more with application development than with the framework around it, so this is all new to me and it took me a while to figure it out.

EF Core, Invalid column name exception after update from netcore2.2 to netcore3.1

Since I've updated from .netcore2.2 to .netcore3.1, I'm facing new weird exception "Invalid column name 'TenantTemplateTypeID1'". Please not the '1', that shouldn't be here. There is NO TenantTemplateTypeID1 anywhere in the code, so I assume it's generated by EF core.
EF core version: 3.1.1
Exception:
Exception: Invalid column name 'TenantTemplateTypeID1'.
Invalid column name 'TenantTemplateTypeID1'.
Invalid column name 'TenantTemplateTypeID1'.
Invalid column name 'TenantTemplateTypeID1'.
StactTrace: at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
Query:
var t = engineCtx.TenantTemplates
.AsNoTracking()
.Include(tt => tt.TenantTemplateParameters)
.Include(tt => tt.TenantTemplateStyles)
.FirstOrDefault(tt => tt.TenantTemplateTypeID == tenantTemplateTypeID);
DbContext
modelBuilder.Entity<Db.Model.TenantTemplate>(entity =>
{
entity.HasKey(e => e.TenantTemplateTypeID)
.HasName("PK_TenantTemplate_TenantTemplateTypeID");
entity.ToTable("TenantTemplates", "templateEngine");
entity.Property(e => e.TenantTemplateTypeID)
.HasColumnName("TenantTemplateTypeId");
//... not related rows removed
entity.HasMany(e => e.TenantTemplateParameters)
.WithOne(e => e.TenantTemplate)
.HasConstraintName("FK_TenantTemplate_TenantTemplateParameters")
.OnDelete(DeleteBehavior.Restrict);
entity.HasMany(e => e.TenantTemplateStyles)
.WithOne(e => e.TenantTemplate)
.HasConstraintName("FK_TenantTemplate_TenantTemplateStyles")
.OnDelete(DeleteBehavior.Restrict);
});
DB model contains only properties without any attributes.
public partial class TenantTemplate
{
public long TenantTemplateTypeID { get; set; }
// not related rows removed
public virtual ICollection<TenantTemplateParameter> TenantTemplateParameters { get; set; }
public virtual ICollection<TenantTemplateStyle> TenantTemplateStyles { get; set; }
}
// TenantTemplateStyle is exacly the same (just PK has a different name)
public class TenantTemplateParameter
{
public long TenantTemplateParameterID { get; set; }
public long TenantTemplateTypeID { get; set; }
// rows removed
public virtual TenantTemplate TenantTemplate { get; set; }
}
Anyone knows some overround? Thanks...
I'm pretty sure this is caused by the following EF Core 3.0 breaking change - The foreign key property convention no longer matches same name as the principal property.
According to that rule, TenantTemplateTypeID in TenantTemplateParameter is not considered as FK name.
The weird thing is that at the same time it is the default conventional FK name according to the rules explained in Foreign key shadow properties:
The property will be named <navigation property name><principal key property name> (the navigation on the dependent entity, which points to the principal entity, is used for the naming). If the principal key property name includes the name of the navigation property, then the name will just be <principal key property name>. If there is no navigation property on the dependent entity, then the principal type name is used in its place.
Since the name is already reserved by the "non FK" property, conventional name builder adds index to the default conventional name, hence the observed behavior.
I would consider this as bug/defect of the current EF Core implementation. The workaround/solution of course is to explicitly map the FK property, e.g.
entity.HasMany(e => e.TenantTemplateParameters)
.WithOne(e => e.TenantTemplate)
.HasForeignKey(e => e.TenantTemplateTypeID) // <--
.HasConstraintName("FK_TenantTemplate_TenantTemplateParameters")
.OnDelete(DeleteBehavior.Restrict);
and similar for the other relationship (with TenantTemplateStyle) if it's using the same naming convention.

Getting multiple errors during Database-Migration with EF6 and .NET 4.6.1

Getting the following errors:
"Validation failed for one or more entities. See 'EntityValidationErrors' property for more details." at line 89 of Configuration.cs
Line 89 is simply a context.SaveChanges();, at the end of the class file:
namespace Model.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<Model.ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(ApplicationDbContext context)
{
SeedData.AdditionalSeed _AdditionalSeed = new SeedData.AdditionalSeed();
_AdditionalSeed.Seed_StateOrProvince(context);
_AdditionalSeed.Seed_CountryOrRegion(context);
_AdditionalSeed.Seed_ContactTypes(context);
_AdditionalSeed.Seed_CurrencyCodes(context);
_AdditionalSeed.Seed_Preferences_CalendarSettings_CalendarView(context);
_AdditionalSeed.Seed_Preferences_CalendarSettings_WeeklyView(context);
_AdditionalSeed.Seed_Preferences_CalendarSettings_WhatToShow(context);
_AdditionalSeed.Seed_Preferences_Calendar_FuturePastSettings(context);
_AdditionalSeed.Seed_Preferences_ItemsAndInventory_AdvancedInventory_Barcodes_CopyBarcodeFrom(context);
_AdditionalSeed.Seed_Preferences_ItemsAndInventory_UnitOfMeasureType(context);
_AdditionalSeed.Seed_Preferences_PayrollEmployees_EmployeeDefaults_PayFrequency(context);
_AdditionalSeed.Seed_Preferences_PayrollEmployees_EmployeeDefaults_SickAndVacationDefaults_AccuralPeriod(context);
_AdditionalSeed.Seed_Preferences_PayrollEmployees_EmployeeDefaults_Taxes_FilingStatus(context);
_AdditionalSeed.Seed_Preferences_ReportsAndGraphs_ReportPreferences_DatePreparedFormat(context);
_AdditionalSeed.Seed_Preferences_ReportsAndGraphs_ReportPreferences_PageNumberFormat(context);
_AdditionalSeed.Seed_Preferences_ReportsAndGraphs_ReportPreferences_PageLayout(context);
_AdditionalSeed.Seed_Preferences_SalesAndCustomers_InvoicePackingSlip(context);
_AdditionalSeed.Seed_Preferences_SalesAndCustomers_RoundingRules(context);
_AdditionalSeed.Seed_Preferences_SalesAndCustomers_Shippers(context);
_AdditionalSeed.Seed_Preferences_SalesTax_Intervals(context);
_AdditionalSeed.Seed_Preferences_SalesTax_SalesTaxCode(context);
_AdditionalSeed.Seed_Preferences_SendForms_SendMethod(context);
_AdditionalSeed.Seed_Preferences_SendForms_DeliveryMethod(context);
_AdditionalSeed.Seed_Preferences_SendForms_EmailTemplateCategoryList(context);
_AdditionalSeed.Seed_Preferences_TimeAndExpenses_WorkWeek(context);
_AdditionalSeed.Seed_Security_RoleMaster(context);
context.SaveChanges();
_AdditionalSeed.Seed_Security_AccountingRole(context);
_AdditionalSeed.Seed_Security_BankingRole(context);
_AdditionalSeed.Seed_Security_CentersRole(context);
_AdditionalSeed.Seed_Security_CompanyRole(context);
_AdditionalSeed.Seed_Security_CustomerAndReceivablesRole(context);
_AdditionalSeed.Seed_Security_EmployeesAndPayrollRole(context);
_AdditionalSeed.Seed_Security_FileRole(context);
_AdditionalSeed.Seed_Security_ListsRole(context);
_AdditionalSeed.Seed_Security_ReportsRole(context);
_AdditionalSeed.Seed_Security_SiteSettingsRole(context);
_AdditionalSeed.Seed_Security_TimeTrackingRole(context);
_AdditionalSeed.Seed_Security_VendorsAndPayablesRole(context);
_AdditionalSeed.Seed_TaxesFilingStatus(context);
_AdditionalSeed.Seed_EmploymentType(context);
_AdditionalSeed.Seed_EmployeeEmergencyContactType(context);
_AdditionalSeed.Seed_EmployeeEthnicity(context);
_AdditionalSeed.Seed_EmployeeMaritalStatus(context);
_AdditionalSeed.Seed_Salutations(context);
_AdditionalSeed.Seed_DefaultUnitUsedForTypes(context);
_AdditionalSeed.Seed_UnitOfMeasureDefaultType(context);
context.SaveChanges();
_AdditionalSeed.Seed_UnitOfMeasureDefaultBaseUnits(context);
context.SaveChanges();
_AdditionalSeed.Seed_UnitOfMeasureDefaultRelatedUnits(context);
context.SaveChanges();
_AdditionalSeed.Seed_Terms(context);
_AdditionalSeed.Seed_TaxForms(context);
context.SaveChanges();
_AdditionalSeed.Seed_TaxFormDetail(context);
_AdditionalSeed.Seed_AccountTypes(context);
context.SaveChanges();
_AdditionalSeed.Seed_Industries(context);
context.SaveChanges();
_AdditionalSeed.Seed_OrganizationTypes(context);
context.SaveChanges();
}
}
}
When I comment this line out I then get the following error:
"Sequence contains more than one element" at line 12 of TaxFormDetail.cs
The code for this class file is over 1200 lines so I will only supply the unique lines:
namespace Model.Migrations.SeedData
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
public partial class AdditionalSeed
{
public void Seed_TaxFormDetail(ApplicationDbContext context)
{
context.TaxFormDetail.AddOrUpdate(
r => new { r.TaxFormID, r.TaxCodeLineDescription },
new TaxFormDetailModel() { TaxCodeLineNumber = null, TaxCodeLineDescription = "Schedule F: Interest expense, mortgage", TaxFormID = context.TaxForms.FirstOrDefault(r => r.TaxForm.Equals("1040")).RecordID },
.....
new TaxFormDetailModel() { TaxCodeLineNumber = null, TaxCodeLineDescription = "Pt I-Unrel Bus Inc: Sch C, L3-Inc-real/pers prop", TaxFormID = context.TaxForms.FirstOrDefault(r => r.TaxForm.Equals("990-T")).RecordID }
);
}
}
}
My model for this table is as follows:
[Table(name: "TaxFormDetail")]
public class TaxFormDetailModel
{
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RecordID { get; set; }
public Guid TaxFormID { get; set; }
public string TaxCodeLineNumber { get; set; }
[MaxLength(255)]
public string TaxCodeLineDescription { get; set; }
}
I have read numerous google and SO results on these errors and nothing helps unless I have missed something.
EDIT
The only error I have is the stack trace as this is run by the Package Manager, There is no other way I know of of getting anything else:
System.InvalidOperationException: Sequence contains more than one element
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__2[TResult](IEnumerable`1 sequence)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
at System.Data.Entity.Migrations.DbSetMigrationsExtensions.AddOrUpdate[TEntity](DbSet`1 set, IEnumerable`1 identifyingProperties, InternalSet`1 internalSet, TEntity[] entities)
at System.Data.Entity.Migrations.DbSetMigrationsExtensions.AddOrUpdate[TEntity](IDbSet`1 set, Expression`1 identifierExpression, TEntity[] entities)
at Model.Migrations.SeedData.AdditionalSeed.Seed_TaxFormDetail(ApplicationDbContext context) in Z:\_Profile Storage\Projects\Corporate.WEB\Model\Migrations\SeedData\TaxFormDetail.cs:line 12
at Model.Migrations.Configuration.Seed(ApplicationDbContext context) in Z:\_Profile Storage\Projects\Corporate.WEB\Model\Migrations\Configuration.cs:line 81
at System.Data.Entity.Migrations.DbMigrationsConfiguration`1.OnSeed(DbContext context)
at System.Data.Entity.Migrations.DbMigrator.SeedDatabase()
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.SeedDatabase()
at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.<Update>b__b()
at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.Run()
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.<.ctor>b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
Sequence contains more than one element
I figured out a workaround for this situation.
For the "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details." at line 89 of Configuration.cs error, I changed the seed method to check for existing data for that specific table (tax code) and if the table was empty, add records NOT add or update.
As far as "Sequence contains more than one element" at line 12 of TaxFormDetail.cs goes, I basically did the same thing.
Now every time I do an Update-Database PM command, I do not get any more errors.

Entity Framework 6 code first: setting unicode to false for string properties

In my model i have some entities decorated with StringLength attribute:
[StringLength(128)]
public string FirstName { get; set; }
Also i have disable unicode for all string properties this way:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Properties<string>().Configure(p => p.IsUnicode(false));
}
The problem is that all string properties decorated with the mentioned attribute are ignoring this setting when generating the database schema, producing nvarchar datatype for the corresponding database columns.
What is the correct way to disable unicode in this cases?
Seems to be a bug (or omission) in the new PropertyConventionConfiguration API. The following configuration does work, so it can serve as a work-around:
modelBuilder.Properties<string>().Configure(x => x.HasColumnType("VARCHAR"));

EntityFramework: How to configure Cascade-Delete to nullify Foreign Keys

EntityFramework's documentation states that the following behavior is possible:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
(from http://msdn.microsoft.com/en-us/jj591620)
However, I cannot achieve such a behavior.
I have the following Entities defined with code-first:
public class TestMaster
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TestChild> Children { get; set; }
}
public class TestChild
{
public int Id { get; set; }
public string Name { get; set; }
public virtual TestMaster Master { get; set; }
public int? MasterId { get; set; }
}
Here is the Fluent API mapping configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TestMaster>()
.HasMany(e => e.Children)
.WithOptional(p => p.Master).WillCascadeOnDelete(false);
modelBuilder.Entity<TestChild>()
.HasOptional(e => e.Master)
.WithMany(e => e.Children)
.HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
}
Foreign Key is nullable, navigation property is mapped as Optional, so I expect the cascade delete to work as described as MSDN - i.e. to nullify MasterID's of all children and then delete the Master object.
But when I actually try to delete, I get the FK violation error:
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Find(1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
On SaveChanges() it throws the following:
System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.
Am I doing something wrong or did I misunderstood what the MSDN says?
It works indeed as described but the article on MSDN misses to emphasize that it only works if the children are loaded into the context as well, not only the parent entity. So, instead of using Find (which only loads the parent) you must use eager loading with Include (or any other way to load the children into the context):
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Include(m => m.Children)
.SingleOrDefault(m => m.Id == 1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
This will delete the master from the database, set all foreign keys in the Child entities to null and write UPDATE statements for the children to the database.
After following #Slauma's great answer I was still getting same error as OP.
So don't be as naive as me and think that the examples below will end up with same result.
dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
// code above will give error and code below will work on dbCtx.SaveChanges()
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;
First load the children into context before setting entity state to deleted (if you are doing it that way).