Dynamic Data + Entity Framework Code First - possible in 4.0?

Topics: General
Sep 17, 2012 at 8:02 PM

I'm trying to set up a simple Dynamic Data site for an Entity Framework Code First model using ASP.NET 4.0 and Entity Framework 5.0, and it seems to be ignoring my model configuration. Every time I run the site, it gives me the standard foreign key cycles error, even though I've disabled cascading for the affected foreign keys.

Do I need to upgrade to ASP.NET 4.5 to get this to work, or is there some magic switch I'm missing to make it work in 4.0?

Sep 17, 2012 at 11:22 PM

any chance you can post your model?

take a look at this for using EF Code First with dynamic data

http://blogs.msdn.com/b/webdev/archive/2012/08/15/using-dynamic-data-with-entity-framework-dbcontext.aspx

 

 

Sep 18, 2012 at 11:32 AM

Thanks for replying. I've already tried your blog post, and I've also tried David Ebbo's DynamicData.EFCodeFirstProvider, but I get the same result with either approach.

The relevant parts of my model are:

public class Employee
{
   [Key, Column("EmployeeId")]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public virtual int Id { get; set; }

   [Required, StringLength(100)]
   public virtual string Name { get; set; }

   public virtual ICollection<EmployeeRole> Roles { get; set; }

   public virtual ICollection<EmployeeRole> Subordinates { get; set; }

   public virtual ICollection<Delegation> DelegatedRoles { get; set; } 
}

internal sealed class EmployeeTypeConfiguration : EntityTypeConfiguration<Employee>, IContextTypeConfiguration
{
   public EmployeeTypeConfiguration()
   {
      HasMany(e => e.Roles).WithRequired(r => r.Employee).HasForeignKey(r => r.EmployeeId);
      HasMany(e => e.Subordinates).WithOptional(r => r.Manager).HasForeignKey(r => r.ManagerId);
   }

   public void Configure(DbModelBuilder modelBuilder)
   {
      modelBuilder.Configurations.Add(this);
   }
}

public class EmployeeRole
{
   [Key, Column("EmployeeRoleId")]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public virtual int Id { get; set; }

   [Required, StringLength(100)]
   public virtual string JobTitle { get; set; }

   public virtual int EmployeeId { get; set; }

   [ForeignKey("EmployeeId")]
   public virtual Employee Employee { get; set; }

   public virtual int? ManagerId { get; set; }

   [ForeignKey("ManagerId")]
   public virtual Employee Manager { get; set; }

   public virtual ICollection<Delegation> Delegations { get; set; }
}

public class Delegation : IValidatableObject
{
   public Delegation()
   {
      Id = Guid.NewGuid();
   }

   [Key, Column("DelegationId")]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public virtual Guid Id { get; set; }

   public virtual DateTime StartDate { get; set; }

   public virtual DateTime EndDate { get; set; }

   public virtual int DelegatedFromId { get; set; }

   public virtual EmployeeRole DelegatedFrom { get; set; }

   public virtual int DelegatedToId { get; set; }

   public virtual Employee DelegatedTo { get; set; }

   public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
   {
      if (StartDate > EndDate)
      {
         yield return new ValidationResult(Resources.Error_StartDateAfterEndDate, new[] { "StartDate", "EndDate" });
      }
   }
}

internal sealed class DelegationTypeConfiguration : EntityTypeConfiguration<Delegation>, IContextTypeConfiguration
{
   public DelegationTypeConfiguration()
   {
      HasRequired(d => d.DelegatedFrom).WithMany(r => r.Delegations).HasForeignKey(d => d.DelegatedFromId).WillCascadeOnDelete(false);
      HasRequired(d => d.DelegatedTo).WithMany(e => e.DelegatedRoles).HasForeignKey(d => d.DelegatedToId).WillCascadeOnDelete(false);
   }

   public void Configure(DbModelBuilder modelBuilder)
   {
      modelBuilder.Configurations.Add(this);
   }
}

public class AuthorityContext : DbContext
{
   public ObjectContext ObjectContext
   {
      get { return ((IObjectContextAdapter)this).ObjectContext; }
   }

   public IDbSet<Employee> Employees
   {
      get { return Set<Employee>(); }
   }

   public IDbSet<EmployeeRole> EmployeeRoles
   {
      get { return Set<EmployeeRole>(); }
   }

   public IDbSet<Delegation> Delegations
   {
      get { return Set<Delegation>(); }
   }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
      base.OnModelCreating(modelBuilder);
      CompositionHost.GlobalContainer.GetExportedValue<TypeConfigurations>().Configure(modelBuilder);
   }

   [Export]
   private sealed class TypeConfigurations
   {
      [ImportMany]
      private IEnumerable<IContextTypeConfiguration> Configurations { get; [UsedImplicitly] set; }

      public void Configure(DbModelBuilder modelBuilder)
      {
         foreach (var item in Configurations)
         {
            item.Configure(modelBuilder);
         }
      }
   }
}

[InheritedExport]
public interface IContextTypeConfiguration
{
   void Configure(DbModelBuilder modelBuilder);
}

The generated SQL script is:

create table [dbo].[Delegations] (
    [DelegationId] [uniqueidentifier] not null default newid(),
    [StartDate] [datetime] not null,
    [EndDate] [datetime] not null,
    [DelegatedFromId] [int] not null,
    [DelegatedToId] [int] not null,
    primary key ([DelegationId])
);
create table [dbo].[Employees] (
    [EmployeeId] [int] not null identity,
    [Name] [nvarchar](100) not null,
    primary key ([EmployeeId])
);
create table [dbo].[EmployeeRoles] (
    [EmployeeRoleId] [int] not null identity,
    [JobTitle] [nvarchar](100) not null,
    [EmployeeId] [int] not null,
    [ManagerId] [int] null,
    primary key ([EmployeeRoleId])
);
alter table [dbo].[Delegations] add constraint [Delegation_DelegatedFrom] foreign key ([DelegatedFromId]) references [dbo].[EmployeeRoles]([EmployeeRoleId]);
alter table [dbo].[Delegations] add constraint [Delegation_DelegatedTo] foreign key ([DelegatedToId]) references [dbo].[Employees]([EmployeeId]);
alter table [dbo].[EmployeeRoles] add constraint [Employee_Roles] foreign key ([EmployeeId]) references [dbo].[Employees]([EmployeeId]) on delete cascade;
alter table [dbo].[EmployeeRoles] add constraint [Employee_Subordinates] foreign key ([ManagerId]) references [dbo].[Employees]([EmployeeId]);
alter table [dbo].[EmployeeRoles] add constraint [Project_Roles] foreign key ([ProjectId]) references [dbo].[Projects]([ProjectId]);

And the error message I get when I try to run the site is:

Introducing FOREIGN KEY constraint 'FK_dbo.Delegations_dbo.Employees_DelegatedToId' on table 'Delegations' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Sep 18, 2012 at 12:55 PM
Edited Sep 18, 2012 at 3:08 PM

Argh! Never mind - after re-reading that, it turned out to be PEBKAC!

I'm using MEF to import the EntityTypeConfiguration/ComplexTypeConfiguration classes, and my default container was using a DirectoryCatalog looking at the directory of the Assembly.GetEntryAssembly().Location path, which doesn't work in ASP.NET. After changing it to look in the bin directory, it seems to be working.

Thanks for your help.

Sep 19, 2012 at 2:29 AM

cool...