Question

I am using EF5 fluent-api to try to set up relationships/constraints between several tables, and I want these relationships to include cascade on delete and I think I'm missing something simple because what I've tried below, produces said error. Long post, but 99% code, not to complicated, but below mentioned error is received when trying to reinitialize my model - there is some constraint that is expected, but not found. Really scratching my head over this...any direction would be most appreciated.

namespace Deals.Core.DataAccess.Entities
{
   public abstract class Entity<TEntity> : IEntity<TEntity> where TEntity : Entity<TEntity>, new()
   {
      private bool isEmpty;

      protected Entity()
      {
         this.isEmpty = false;
      }

      public static TEntity Empty
      {
         get
         {
            return new TEntity() { IsEmpty = true };
         }
      }

      public bool Active { get; set; }
      public bool Deleted { get; set; }

      [NotMapped]
      public bool IsEmpty
      {
         get
         {
            return this.isEmpty;
         }

         protected set
         {
            this.isEmpty = value;
         }
      }

      public int Version { get; set; }
   }

   public class Site : Entity<Site>, ISite
   {
      public Guid Id { get; set; }
      public virtual ICollection<User> Users { get; set; }
      public virtual Survey Survey { get; set; }
   }

   public class Survey : Entity<Survey>, ISurvey
   { 
      public Guid Id { get; set; }
      public virtual Site Site { get; set; }
   }

   public class User : Entity<User>, IUser
   {
      public Guid Id { get; set; }
      public virtual UserProfile UserProfile { get; set; }
      public Guid SiteId { get; set; }
      public virtual Site Site { get; set; }
   }

   public class UserProfile : Entity<UserProfile>, IUserProfile
   {
      public Guid Id { get; set; }
      public virtual User User { get; set; }
   }
}

namespace Deals.Core.DataAccess.Models
{
   public class Context : DbContext, IContext
   {
      public DbSet<Site> Sites { get; set; }
      public DbSet<Survey> Surveys { get; set; }
      public DbSet<User> Users { get; set; }
      public DbSet<UserProfile> UserProfiles { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         this.MapSite(modelBuilder);
         this.MapSurvey(modelBuilder);
         this.MapUser(modelBuilder);
         this.MapUserProfile(modelBuilder);

         Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, ContextConfiguration>());

         base.OnModelCreating(modelBuilder);
      }

      protected virtual void MapSite(DbModelBuilder modelBuilder)
      {
         // Ignore the IsEmpty property.
         this.MapEntity<Site>(modelBuilder);

         modelBuilder.Entity<Site>().HasKey(p => p.Id);
         modelBuilder.Entity<Site>().HasOptional(p => p.Survey);
         modelBuilder.Entity<Site>().HasOptional(p => p.Users);
      }

      protected virtual void MapUser(DbModelBuilder modelBuilder)
      {
         // Ignore the IsEmpty property.
         this.MapEntity<User>(modelBuilder);

         modelBuilder.Entity<User>().HasKey(p => p.Id);
         modelBuilder.Entity<User>().HasRequired(p => p.Site).WithMany(p => p.Users).HasForeignKey(p => p.SiteId);
      }

      protected virtual void MapUserProfile(DbModelBuilder modelBuilder)
      {
         // Ignore the IsEmpty property.
         this.MapEntity<UserProfile>(modelBuilder);

         modelBuilder.Entity<UserProfile>().HasKey(p => p.Id);

         // Why does adding .WillCascadeOnDelete() look for a user_id constraint on UserProfile that does not exist?
         //modelBuilder.Entity<UserProfile>().HasRequired(p => p.User).WithRequiredPrincipal(user => user.UserProfile);
         //// .WillCascadeOnDelete();
         modelBuilder.Entity<UserProfile>().HasRequired(p => p.User);
      }

      protected virtual void MapSurvey(DbModelBuilder modelBuilder)
      {
         // Ignore the IsEmpty property.
         this.MapEntity<Survey>(modelBuilder);

         modelBuilder.Entity<Survey>().HasKey(p => p.Id);
         modelBuilder.Entity<Survey>().HasRequired(p => p.Site);
         //modelBuilder.Entity<Site>().HasOptional(p => p.Survey).WithOptionalPrincipal().WillCascadeOnDelete();
         modelBuilder.Entity<Survey>().Property(p => p.SurveyXml).HasColumnType("xml").IsRequired();
      }

      #region Generic Mapping
      protected virtual void MapEntity<T>(DbModelBuilder modelBuilder) where T : Entity<T>, new()
      {
         // Ignore the IsEmpty property.
         modelBuilder.Entity<T>()
            .Ignore(p => p.IsEmpty);
      }

      #endregion Generic Mapping
   }
}

SQL generated:

create table [dbo].[Sites] (
    [Id] [uniqueidentifier] not null,
    [Url] [nvarchar](max) null,
    [Description] [nvarchar](max) null,
    [Active] [bit] not null,
    [Deleted] [bit] not null,
    [Version] [int] not null,
    primary key ([Id])
);
create table [dbo].[Surveys] (
    [Id] [uniqueidentifier] not null,
    [SurveyXml] [xml] not null,
    [Active] [bit] not null,
    [Deleted] [bit] not null,
    [Version] [int] not null,
    primary key ([Id])
);
create table [dbo].[Users] (
    [Id] [uniqueidentifier] not null,
    [UserName] [nvarchar](max) null,
    [Password] [nvarchar](max) null,
    [LastLogin] [datetime] not null,
    [SiteId] [uniqueidentifier] not null,
    [Active] [bit] not null,
    [Deleted] [bit] not null,
    [Version] [int] not null,
    primary key ([Id])
);
create table [dbo].[UserProfiles] (
    [Id] [uniqueidentifier] not null,
    [FirstName] [nvarchar](max) null,
    [LastName] [nvarchar](max) null,
    [MiddleInitial] [nvarchar](max) null,
    [Honorific] [nvarchar](max) null,
    [Email] [nvarchar](max) null,
    [Active] [bit] not null,
    [Deleted] [bit] not null,
    [Version] [int] not null,
    primary key ([Id])
);

Сan't get "on delete cascade" here:

alter table [dbo].[Surveys] add constraint [Site_Survey] foreign key ([Id]) references [dbo].[Sites]([Id]); 

This is good:

alter table [dbo].[Users] add constraint [User_Site] foreign key ([SiteId]) references [dbo].[Sites]([Id]) on delete cascade;

Can't get "on delete cascade" here:

alter table [dbo].[UserProfiles] add constraint [UserProfile_User] foreign key ([Id]) references [dbo].[Users]([Id]); 

If I do this:

 modelBuilder.Entity<UserProfile>()
      .HasRequired(p => // p.User)
      .WithRequiredPrincipal(user => user.UserProfile)
      .WillCascadeOnDelete();

 // Instead of this:
 modelBuilder.Entity<UserProfile>().HasRequired(p => p.User);

 // The sql that is generated looks correct:
 alter table [dbo].[Users] add constraint [UserProfile_User] 
    foreign key ([Id]) references [dbo].[UserProfiles]([Id]) on delete cascade;

However, I get this error when trying reinitialize my model when running tests; what's up with FK_dbo.UserProfiles_dbo.Users_Id?

Test Name:  SiteRepository_Remove_TestPasses
Test FullName:  Deals.Core.Tests.Deals.Core.DataLibrary.Tests.Integration.SiteRepositoryIntegrationTests.SiteRepository_Remove_TestPasses
Test Source:    c:\Dev\Deals\Deals.Core.Tests\Deals.Core.DataLibrary.Tests\Integration\SiteRepository.Integration.Tests.cs : line 51
Test Outcome:   Failed
Test Duration:  0:00:01.4034458

Result Message: 
Initialization method Deals.Core.Tests.Deals.Core.DataLibrary.Tests.Integration.SiteRepositoryIntegrationTests.TestInitialize threw exception. System.Data.SqlClient.SqlException: System.Data.SqlClient.SqlException: 'FK_dbo.UserProfiles_dbo.Users_Id' is not a constraint.
Could not drop constraint. See previous errors..
Result StackTrace:  
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
   at System.Data.Entity.Migrations.DbMigrator.AutoMigrate(String migrationId, XDocument sourceModel, XDocument targetModel, Boolean downgrading)
   at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context)
   at System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
   at System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
   at Deals.Core.DataAccess.UnitOfWorkCore.ForceDatabaseInititialization() in c:\Dev\Deals\Deals.Core.DataAccess\UnitOfWorkCore.cs:line 164
   at Deals.Core.Tests.Deals.Core.DataLibrary.Tests.Integration.SiteRepositoryIntegrationTests.TestInitialize() in c:\Dev\Deals\Deals.Core.Tests\Deals.Core.DataLibrary.Tests\Integration\SiteRepository.Integration.Tests.cs:line 28
Was it helpful?

Solution 2

This fixed it

It appears that my database was not being dropped and recreated every test run...

In my DbContext

protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, ContextConfiguration>());

         base.OnModelCreating(modelBuilder);
      }

My Migration Configuration

public class ContextConfiguration : DbMigrationsConfiguration<Context>
   {
      public ContextConfiguration()
      {
         this.AutomaticMigrationsEnabled = true;
         this.AutomaticMigrationDataLossAllowed = true;
      }
   }

In My Tests

[TestInitialize]
      public override void TestInitialize()
      {
         // Force model updates.
         using (var uow = UnitOfWorkFactory.Instance.Create<UnitOfWorkCore>(DefaultConnectionString))
         {
            uow.Database.Initialize(force: false);
         }

         // begin transaction
         this.transactionScope = new TransactionScope();

         this.unitOfWork = UnitOfWorkFactory.Instance.Create<UnitOfWorkCore>(DefaultConnectionString);
      }

I changed things to this and not no problems *I changed things to this and not no problems* I changed things to this and not no problems

In My Tests

[TestInitialize]
      public override void TestInitialize()
      {
         // Force model updates.

         // I CHANGED TO THIS:
         Database.SetInitializer(new DropCreateDatabaseAlways<Context>());

         using (var uow = UnitOfWorkFactory.Instance.Create<UnitOfWorkCore>(DefaultConnectionString))
         {
            uow.Database.Initialize(force: false);
         }

         // begin transaction
         this.transactionScope = new TransactionScope();

         this.unitOfWork = UnitOfWorkFactory.Instance.Create<UnitOfWorkCore>(DefaultConnectionString);
      }

OTHER TIPS

If you want to delete Child Object when Parent Object is deleted, You must configure it from the Parent side of the association. First make Foreign Key properties in child objects [Required] then the following code.

You should also know that unlike one-to-many relationships in one-to-one relationships cascading delete is not enabled by default, not even for required relationships. You must always define cascading delete for a one-to-one relationship explicitly:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<UserProfile>()
                .HasRequired(u => u.User)
                .WithRequiredPrincipal(c => c.UserProfile)
                .WillCascadeOnDelete(true);

    modelBuilder.Entity<Survey>()
                .HasRequired(a => a.Site)
                .WithRequiredPrincipal(c => c.Survey)
                .WillCascadeOnDelete(true);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top