I've got the following basic domain model for my MVC website accounts:
public class Account
{
public Account()
{
Details = new AccountDetails( this );
Logon = new LogonDetails(this);
}
public virtual int Id { get; private set; }
public virtual AccountDetails Details { get; set; }
public virtual LogonDetails Logon { get; set; }
...
}
public class AccountDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime Created { get; set; }
...
}
public class LogonDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime? LastLogon { get; set; }
...
}
Both AccountDetails and LogonDetails use a mapping like this:
public class AccountDetailsOverride : IAutoMappingOverride<AccountDetails>
{
public void Override( AutoMap<AccountDetails> mapping )
{
mapping
.UseCompositeId()
.WithKeyReference( x => x.Account, "AccountId" );
mapping.IgnoreProperty( x => x.Account );
}
}
I've split the account details and logon details into separate models since I rarely need that information, whereas I need the userid and name for many site operations and authorization. I want the Details and Logon properties to be lazy-loaded only when needed. With my current mapping attempts I can get one of two behaviors:
# 1 Create table and load successfully, cannot save
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.WithColumns( x => x.Account.Id )
.Cascade.All();
mapping
.References( x => x.Logon )
.WithColumns( x => x.Account.Id )
.Cascade.All();
}
}
The tables are generated as expected. Existing data loads correctly into the model, but I can't save. Instead I get an index out of range exception. Presumably because Account.Details and Account.Logon are both trying to use the same db field for their reference (The Account.Id itself).
#2 Table includes extra fields, does not save properly
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.Cascade.All();
mapping
.References( x => x.Logon )
.Cascade.All();
}
}
I get a table with a separate field for Details_id and Logon_id but they are null since the value of Details.Account.Id is null when the Account is persisted. So, attempting to Session.Get the account results in Details and Logon being null. If I save Account twice, the table is updated correctly and I can load it.
Help...
There must be a way of mapping this hierarchy and I'm missing something simple. Is there a way to help nhibernate pick the proper field (to solve #1) or to have it update dependent fields automatically after save (to solve#2)?
Thanks for any insight you folks can provide.
If I'm understanding your model and desired behavior, what you have is actually a one-to-one relationship between Account and AccountDetails and between Account and LogonDetails. References creates a many-to-one relationship, so that could be your problem; try HasOne instead.
That said, for this and other reasons, I avoid one-to-ones unless absolutely necessary. There may be more than what you're showing, but is it worth the headache and ugly model to avoid loading two DateTime fields?
Finally, and this is somewhat speculation since I have not tested this functionality, NHibernate 2.1's (which FNH has switched to as supported version) mapping XML schema defines a lazy attribute for property elements. The 1.0 release of FNH (should be in the next week or two) will support setting this attribute. As I said, I have not tested it but it would seem that this would allow you to lazy load individual properties which is exactly what you want.
Related
Before I elaborate the problem, I'm well aware the database isn't designed conventionally. Sadly, I can't change this particular database due to how it is integrated, so I've got a potential solution but that won't be implemented for several months. In the mean time I need to work around the following:
The problem is I need to build an Entity, this would represent our Accounts. But the problem, our database implements the following structure:
Invoiced Table
Non-Invoiced Table
My Entity, represents the exact same data on those tables, same column names, duplicate under all conditions, except one is invoiced while the other represents non-invoiced customers. But since it isn't one table, with a Flag to indicate invoiced versus non-invoiced, how can my Entity link to both of those tables?
Since both tables represent separate names, I can't use the [Table("...")] or the auto mapping capabilities. I hate asking such a question, but I can't find any documentation on how to handle such an issue.
You could use table-per-concrete class inheritance then define the table names on the derived types:
public abstract class Account
{
// common entity code here
...
}
public class InvoicedAccount : Account {}
public class NonInvoicedAccount: Account {}
public YourContext : DbContext
{
public DbSet<InvoicedAccount> InvoicedAccounts { get; set; }
public DbSet<NonInvoicedAccount> NonInvoicedAccounts { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<InvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "InvoicedAccountTable" );
} );
modelBuilder.Entity<NonInvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "NonInvoicedAccountTable" );
} );
}
}
There are a few questions out there on this topic, but my question is very specific to true self referencing. All the examples for other questions are circular references and that doesn't help me in this case.
Lets say I have this model:
public class User
{
[Key]
public int Id { get; set; }
...
public int CreatedByUserId { get; set; }
}
and this map:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
this.HasRequired(a => a.CreatedByUser)
.WithMany()
.HasForeignKey(u => u.CreatedByUserId);
}
}
After migrations generates a database with this code I can manually add a User in SQL Management Studio with Id = 1, and CreatedByUserId = 1 so that tells me that self references like this can work.
However when using EF to create a user, I run into a "unable to determine a valid ordering for dependent operations" issue. There are a lot of questions on the matter that involve a new entity that refers another new entity that has a foreign key on the first entity, which is a circular reference. The solution in those cases is either save one of entities first or to have a nullable id on the circular entity foreign key. I can not do either of those because the first would be impossible and the second is a external constraint that I cannot have nullable ids.
So, seeing how I can achieve this by adding a entry manually I can assume it's a limitation of EF5. What are the work arounds?
You can still satisfy your interface and do the save first then set method by adding another property to act as a nullable backer for CreatedByUserId:
public class User : ICreatable
{
[Key]
public int Id { get; set; }
...
public int CreatedByUserId
{
get
{
if (!_CreatedByUserId.HasValue)
//throw new exception, something went wrong.
return _CreatedByUserId;
}
set
{
_CreatedByUserId = value;
}
}
int? _CreatedByUserId { get; set; }
}
You may want to rethink the possibility that a user can create him or herself...
However if you really want to do this then there is a solution. Your main problem is the fact that your column is an IDENTITY column which means that EF doesn't specify the Id, SQL server is giving each row an auto-incrementing Id. Any value you set as the Id is ignored. You don't necessarily know when executing the INSERT what the next Id is going to be so you can't create a reference to a row that doesn't exist yet.
Change your mapping code to something like the following:
this.Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.HasRequired(x => x.CreatedByUser)
.WithMany();
You don't need to specify the foreign key if the name pattern matches (eg. CreatedByUser and CreatedByUserId).
Now when you insert a User you can specify the Id and the CreatedById. Although note that you must now always specify the Id to insert a new User. This is common practice if you are using GUIDs as Ids because you can just generate a new GUID without having to first query for the next "available" Id before creating a new object.
I have a situation with EF5 and a complex object. The basics is that I have a parent to child complex object, but the child refers back to the parent, more than once. I have tried various options but am not finding a solution that answers the question. The closest I have got is this answer (option 2c)
My model looks like below:
public class StaffMember
{
public virtual Guid StafId { get; set; }
// other props
// List of leave apps (Approved/Cancelled etc)
public virtual ICollection<StaffLeaveApp> LeaveApps { get; set; }
}
//Staff Leave Application
public class StaffLeaveApp
{
public virtual Guid LeaveId { get; set; }
public virtual Guid StaffId { get; set; }
// other props...
// Leave approved by? (2 approvals required)
public virtual StaffMember ApprovedBy1 { get; set; }
public virtual StaffMember ApprovedBy2 { get; set; }
}
my mappings look like this
public class StaffMap : EntityTypeConfiguration<StaffMember>
{
public StaffMap()
{
ToTable("tblStaffMembers");
HasKey(x => x.StaffId);
// other mappings...
HasMany(x => x.LeaveApps);
}
}
public class StaffLeaveAppMap: EntityTypeConfiguration<StaffLeaveApp>
{
public StaffLeaveAppMap()
{
ToTable("tblStaffMembersLeaveApps");
HasKey(x => x.LeaveId);
Property(x => x.StaffId).HasColumnName("StaffID");
//Child Relationships
HasOptional(x => x.ApprovedBy1).WithMany().Map(m => m.MapKey("LeaveApprovedBy1"));
HasOptional(x => x.ApprovedBy2).WithMany().Map(m => m.MapKey("LeaveApprovedBy2"));
}
}
Table (sorry, no images)
StaffID uniqueidentifier (FK - tblStaffMembers)
LeaveID uniqueidentifier (PK)
LeaveApprovedBy1 uniqueidentifier (FK - tblStaffMembers)
LeaveApprovedBy2 uniqueidentifier (FK - tblStaffMembers)
The business rule says: a staff member has "many" leave applications and a leave application belongs to a single staff member. Each application requires the approval of 2 staff members (managers) before it is "approved".
How would I map the above using EF so that a single staff member has a "many" leave applications (working already) and a leave application is mapped back to a staff member whom approved it for the first approval and then again for the seond approval. If I use the one mapping for "ApprovedBy1" only then EF is happy and all works as expected. The moment I add the second approval mapping EF struggles with the SQL queries it generates.
I am not sure how to tell EF to map back to the StaffMembers table to specify whom approved the application at level 1 and whom approved it at level 2. It almost ends up being a many to many relationship.
Any ideas?
You are looking for the inverse property, which is the property at the other end of an association. In EF, there are two way to mark a property as inverse.
Data annotations: InversePropertyAttribute.
Fluent mapping
As you already have fluent mapping I'll show you how you'd do it there:
HasOptional(x => x.ApprovedBy1).WithMany(x => x.LeaveApps)
.HasForeignKey(s => s.StaffId);
HasOptional(x => x.ApprovedBy2).WithMany()
.Map(m => m.MapKey("LeaveApprovedBy2"));
The HasOptional(...).WithMany(...) pair is a way to map inverse properties. Coming from the other side you can use e.g. HasMany(....).WithOptional(...).
I have two POCO classes:
Order Class:
public class Order
{
public int Id { get; set; }
public int? QuotationId { get; set; }
public virtual Quotation Quotation { get; set; }
....
}
Quotation Class:
public class Quotation
{
public int Id { get; set; }
public virtual Order Order { get; set; }
....
}
Each Order may be made from one or zero quotation, and
each quotation may cause an order.
So I have an "one or zero" to "one or zero" relation, how can I implement this, in EF Code first by Fluent API?
By changing pocos to:
public class Order
{
public int OrderId { get; set; }
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
public int QuotationId { get; set; }
public virtual Order Order { get; set; }
}
and using these mapping files:
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
this.HasOptional(x => x.Quotation)
.WithOptionalPrincipal()
.Map(x => x.MapKey("OrderId"));
}
}
public class QuotationMap : EntityTypeConfiguration<Quotation>
{
public QuotationMap()
{
this.HasOptional(x => x.Order)
.WithOptionalPrincipal()
.Map(x => x.MapKey("QuotationId"));
}
}
we will have this DB(that means 0..1-0..1):
with special thanks to (Vahid Nasiri)
#Masoud's procedure was:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal()
.Map(o => o.MapKey("OrderId"));
modelBuilder.Entity<Quotation>()
.HasOptional(o => o.Order)
.WithOptionalPrincipal()
.Map(o => o.MapKey("QuotationId"));
It gives:
By changing the code to:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(o=> o.Order);
It gives:
See http://msdn.microsoft.com/en-us/data/jj591620 EF Relationships
An excellent Book
http://my.safaribooksonline.com/book/-/9781449317867
Here is a post from developer from Dec 2010. But still relevant
http://social.msdn.microsoft.com/Forums/uk/adonetefx/thread/aed3b3f5-c150-4131-a686-1bf547a68804
The above article is a nice summary or the possible combinations here.
A solution where dependant Table has key from Primary table is possible.
If you Want Independent Keys where both are Principals in a PK/FK scenario, i dont think you can do it in Code first with Fluent API. If they share a Key, You are OK.
1:1 optional assumes the dependent uses the key from Primary.
But since you need to save one of the tables before the other. You can check one of the Foreign Keys with code. OR add teh second Foreign to Database after Code first has created it.
You will get close. But EF will complain about conflicting Foreign keys if you want both to be Foreign keys. Essentially the A depends on B depends A EF doesnt like, even if the columns are nullable and technically possible on the DB.
Here use this test program to try it. Just comment in an out the Fluent API stuff to try some options.
I could NOT get EF5.0 to work with INDEPENDENT PK/FK 0:1 to 0:1
But of course there are reasonable compromises as discussed.
using System.Data.Entity;
using System.Linq;
namespace EF_DEMO
{
class Program
{
static void Main(string[] args) {
var ctx = new DemoContext();
var ord = ctx.Orders.FirstOrDefault();
//. DB should be there now...
}
}
public class Order
{
public int Id {get;set;}
public string Code {get;set;}
public int? QuotationId { get; set; } //optional since it is nullable
public virtual Quotation Quotation { get; set; }
//....
}
public class Quotation
{
public int Id {get;set;}
public string Code{get;set;}
// public int? OrderId { get; set; } //optional since it is nullable
public virtual Order Order { get; set; }
//...
}
public class DemoContext : DbContext
{
static DemoContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DemoContext>());
}
public DemoContext()
: base("Name=Demo") { }
public DbSet<Order> Orders { get; set; }
public DbSet<Quotation> Quotations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasKey(t => t.Id)
.HasOptional(t => t.Quotation)
.WithOptionalPrincipal(d => d.Order)
.Map(t => t.MapKey("OrderId")); // declaring here via MAP means NOT declared in POCO
modelBuilder.Entity<Quotation>().HasKey(t => t.Id)
.HasOptional(q => q.Order)
// .WithOptionalPrincipal(p => p.Quotation) //as both Principals
// .WithOptionalDependent(p => p.Quotation) // as the dependent
// .Map(t => t.MapKey("QuotationId")); done in POCO.
;
}
}
}
Adapted from this answer, try this.
First, fix your classes:
public class Order
{
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
// other properties
}
public class Quotation
{
public int Id {get; set;}
public virtual Order Order { get; set; }
// other properties
}
Then use the fluent API like that:
modelBuilder.Entity<Quotation>()
.HasOptional(quote => quote.Order)
.WithRequired(order=> order.Quotation);
Basically, for 1:1 or [0/1]:[0/1] relationships, EF needs the primary keys to be shared.
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
The Key Attribute
There's a one-to-zero-or-one relationship between the Instructor and the OfficeAssignment entities. An office assignment only exists in relation to the instructor it's assigned to, and therefore its primary key is also its foreign key to the Instructor entity. But the Entity Framework can't automatically recognize InstructorID as the primary key of this entity because its name doesn't follow the ID or classnameID naming convention. Therefore, the Key attribute is used to identify it as the key:
https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
using DataAnnotations:
public class Order
{
[Key]
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
[Key, ForeignKey(nameof(Order))]
public int Id {get; set;}
public virtual Order Order { get; set; }
}
(Note that this is using EF 6.4.4.)
It's fairly straightforward to specify, as long as you don't want foreign key properties:
modelBuilder
.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(q => q.Order);
modelBuilder
.Entity<Quotation>()
.HasOptional(q => q.Order)
.WithOptionalDependent(o => o.Quotation);
Notice here the usage of both WithOptionalPrincipal and WithOptionalDependent. This should give you a single foreign key column on the dependent side (Quotation in the example), but with no foreign key properties. If you want the foreign key on the other side, switch "Dependent" and "Principal" around.
(Note that it is not necessary to have both definitions above; WithOptionalDependent will imply the other side is the principal and vice-versa, so you can use only one of them if you wanted, but I find specifying the relationships from both sides helps prevent errors by double declaring things; any conflict will result in a model error to let you know you missed something.)
While there is an index on the foreign key column, the index does not have a unique constraint. While it is possible to add your own unique constraint (which would require a Key IS NOT NULL filter), it doesn't seem to work and you will get exceptions when updating relationships in some cases. I think this is related to the "swapping problem" where EF will perform its updates in separate queries, so enforcing uniqueness would prevent EF from "moving" a key in two steps.
EF seems to handle the association itself internally, without a unique DB constraint:
On either side, assigning an already used reference results in the other usage of the reference being removed automatically. (So if it is already the case that A1 <=> B1 when you opened the context, and then you write A1 => B2, then A1 <=> B1 is removed and A1 <=> B2 is added, regardless of which side you're on.)
If you try to create a duplicate key by assigning the same reference more than once, EF will throw an exception saying "multiplicity constraint violation". (So in the same context, you wrote both A1 => B1 and A2 => B1, or some similar conflicting mapping.)
If you update the DB manually to create a duplicate key situation, when EF encounters this it will throw an exception saying "A relationship multiplicity constraint violation occurred...this is a non-recoverable error."
It does not seem possible in EF6 to map a property to the foreign key column (at least with Fluent API). Attempting to do so results in a non-unique column name exception since it tries to use the same name for both the property and the association separately.
Note also that it is technically incorrect to have two foreign keys (ie: one on both sides). Such an arrangement would actually be two 0..1 to 0..1 associations since there would be nothing to say that keys on both ends should match. This could maybe work if you enforce the relationship some other way, through the UI and/or possibly a database constraint of some kind.
I also notice that there may be a misunderstanding/miscommunication of exactly what a 0..1 to 0..1 association is. What this means, from my understanding and the way EF seems to consider it as well, is that it is a 1 to 1 association that is optional on both sides. So, you can have objects on either side with no relationship. (Whereas a 1 to 0..1 assocation, objects on one side could exist without a relationship, but objects on the other side would always need an object to relate to.)
But 0..1 to 0..1 does not mean that you can have the association travel in one direction and not the other. If A1 => B1, then B1 => A1 (A1 <=> B1). You cannot assign B1 to A1 without also making A1 relate to B1. This is why it is possible for this association to use only a single foreign key. I think some people may be trying to have an association in which this is not true (A1 relates to B1 but B1 does not relate to A1). But that is really not one association but two 0..1 to 0..1 associations.
Error:
Unhandled Exception: System.Data.SqlClient.SqlException: The operation failed because an index or statistics with name 'IX_ID' already exists on table 'PrivateMakeUpLessons'.
Model (Simplified, building in a separate test project for debugging):
public abstract class Lesson
{
public Guid ID { get; set; }
public string Room { get; set; }
public TimeSpan Time { get; set; }
public int Duration { get; set; }
}
public abstract class RecurringLesson : Lesson
{
public int DayOfWeek { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Frequency { get; set; }
}
public class PrivateLesson : RecurringLesson
{
public string Student { get; set; }
public string Teacher { get; set; }
public virtual ICollection<Cancellation> Cancellations { get; set; }
}
public class Cancellation
{
public Guid ID { get; set; }
public DateTime Date { get; set; }
public virtual PrivateLesson Lesson { get; set; }
public virtual MakeUpLesson MakeUpLesson { get; set; }
}
public class MakeUpLesson : Lesson
{
public DateTime Date { get; set; }
public string Teacher { get; set; }
public virtual Cancellation Cancellation { get; set; }
}
Configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
modelBuilder.Entity<Cancellation>()
.HasOptional(x => x.MakeUpLesson)
.WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
Notes:
This worked fine in EF 4.2. Is there something wrong with my model? The actual model is much more complicated which is why I have all the classes abstracted out. Also, I am working against an existing database so I need to use Table-Per-Type inheritance.
If I change the relationship of Cancellation to PrivateMakeUpLesson from 1 to 0..1 to 0..1 to 0..1 it works. This is undesirable because you can't have a PrivateMakeUpLesson without a Cancellation.
Also, if I make PrivateMakeUpLesson NOT inherit from Lesson then it also works, but it IS a lesson and needs to remain so for existing business logic.
I'd appreciate any guidance. Thank you!
Edit:
Starting a bounty. I can't find any documentation on what changed between EF 4.2 and EF 4.3 with regard to the index generation for code first. It's clear that EF 4.3 is creating more indexes and that the naming scheme has changed but I want to know if there's a bug in EF or if there is something fundamentally wrong with my model or fluent API configuration.
As of EF 4.3, indexes are added for freign key columns during database creation. There is a bug that can cause an index to be created more than once. This will be fixed in a future EF release.
Until then, you can work around the issue by creating your database using Migrations instead of database initializers (or the Database.Create() method).
After generating the initial migration, you will need to delete the redundant call to Index().
CreateTable(
"dbo.PrivateMakeUpLessons",
c => new
{
ID = c.Guid(nullable: false),
...
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Lessons", t => t.ID)
.ForeignKey("dbo.Cancellations", t => t.ID)
.Index(t => t.ID)
.Index(t => t.ID); // <-- Remove this
To continue creating your database at run-time, you can use the MigrateDatabaseToLatestVersion initializer.
In my opinion this is clearly a bug.
The problem starts with the observation that EF creates an index IX_ID at all. If you strip down the model to the following...
public abstract class Lesson
{
public Guid ID { get; set; }
}
public class RecurringLesson : Lesson
{
}
public class MyContext : DbContext
{
public DbSet<Lesson> Lessons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
}
}
... and let EF create the database schema you get two tables Lessons and RecurringLessons as expected for a TPT inheritance mapping. But I am wondering why it creates two indices for the table RecurringLessons:
Index PK_RecurringLessons (clustered, unique) with Index column ID
Index IX_ID (not clustered, not unique) with Index column ID again
I don't know if there is any benefit for the database to have a second index on the same column. But for my understanding it doesn't make sense 1) to create an index on the same column that is already covered in the PK clustered index, and 2) to create a not unique index on a column which is the primary key and therefore necessarily unique.
Moreover due to the one-to-one relationship EF tries to create an index on the table of the dependent of this association which is PrivateMakeUpLessons. (It's the dependent (and not the principal) because Cancellation is required in entity MakeUpLesson.)
ID is the foreign key in this association (and primary key at the same time because one-to-one relationships are always shared primary key associations in Entity Framework). EF apparently always creates a index on the foreign key of a relationship. But for one-to-many relationships this is not a problem because the FK column is different from the PK column. Not so for one-to-one relatonships: The FK and PK are the same (that is ID), hence EF tries to create an index IX_ID for this one-to-one relationship which already exists due to the TPT inheritance mapping (which leads to a one-to-one relationship as well from database perspective).
The same consideration as above applies here: The table PrivateMakeUpLessons has a clustered PK index on column ID. Why is a second index IX_ID on the same column required at all?
In addition EF doesn't seem to check that it already wants to create an Index with name IX_ID for the TPT inheritance, leading finally to the exception in the database when the DDL is sent to create the database schema.
EF 4.2 (and before) didn't create any indices (except PK indices) at all, this was introduced in EF 4.3, especially indices for FK columns.
I didn't find a workaround. In the worst case you have to create the database schema manually and avoid that EF tries to create it (= disable database initialization). In the best case there is a way to disable automatic FK index creation, but I don't know if it's possible.
You can submit a bug report here: http://connect.microsoft.com/VisualStudio
Or maybe someone from EF development team will see your question here and provide a solution.
I got a very similar error to this one in my code a while back. Try putting the cancellation list inside the Lesson class. That's what solved my problem.
Below I describe 2 scenarios what is probably going wrong. Please read in depth by clicking the links I provided to know more about my explanation.
First
Lesson and RecurringLesson are abstract classes (so you want to have it as the base classes).
You are creating a table of the Lesson and the RecurringLesson entities which will result in a Table per hierarchy structure.
brief description
Creating a class of the base table will result in one big table which contains the columns of all inherited tables. So all properties of PrivateLesson, MakeUpLesson and all others inherited entities will be stored in the Lessons table. EF will add also a Discriminator column. The value of this column defaults to the persistent class name (like "PrivateLesson" or "MakeUpLesson") only the column matching to that particular entity (matching the Discriminator value) will be used in that particular row.
BUT
You are also mapping the inherited classes like PrivateLesson and MakeUpLesson. This will force EF to use the Table per Type structure which results in one table per class. This can cause conflicts you are facing right now.
Second
Your example shows you have an one-to-one relationship (Cancellation -> MakeUpLesson) and a one-to-many relationship (Cancellation -> PrivateLesson) because PrivateLesson and MakeUpLessonare both (indirect) inherited from Lesson in combination with the first described scenario can cause problems because it will result in 2 foreign key relationships in the database per entity. (one using Table per hierarchy structure and one using the Table per Type structure).
Also this post can help you defining a correct one-to-one definition.
Please verify by performing the following steps:
I assume you have your own test environment so you can create new test databases
1.
Delete the relationships to the Cancellation by commenting out all properties to this class:
public class PrivateLesson : RecurringLesson
{
public string Student { get; set; }
public string Teacher { get; set; }
//public virtual ICollection<Cancellation> Cancellations { get; set; }
}
public class Cancellation
{
public Guid ID { get; set; }
public DateTime Date { get; set; }
//public virtual PrivateLesson Lesson { get; set; }
//public virtual MakeUpLesson MakeUpLesson { get; set; }
}
public class MakeUpLesson : Lesson
{
public DateTime Date { get; set; }
public string Teacher { get; set; }
//public virtual Cancellation Cancellation { get; set; }
}
And remove the configuration to it:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
//modelBuilder.Entity<Cancellation>()
// .HasOptional(x => x.MakeUpLesson)
// .WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
2.
Create a new empty database
3.
Let EF generate the table structure for you in this empty database.
4.
Verify the first scenario. If that's true this need to be fixed first by using the Table per hierarchy structure OR the Table per Type structure. Probably you want to use the Table per hierarchy structure because (if I understand your question well) there is already an production environment.
When my project was updated from EF 6.0.2 to EF 6.1.1, I had such a problem, then back to 6.0.2, after the return of an older version, the error disappeared