Is it possible to add WillCascadeOnDelete to one side of a many to many relationship - entity-framework-4.1

I have the following model:
public class List
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<ListRules> ListRule { get; set; }
}
public class ListRule
{
public virtual int Id { get; set; }
public virtual List List { get; set; }
public virtual ICollection<List> Lists { get; set; }
}
A List can have many ListRules. A ListRule has to belong to one list. A ListRule can also have 0, 1 or many Lists associated with it.
I tried the following bindings:
modelBuilder.Entity<ListRule>()
.HasRequired(x => x.List)
.WithMany(x => x.ListRule)
.Map(x => x.MapKey("ListId"));
modelBuilder.Entity<ListRule>()
.HasMany(x => x.Lists)
.WithMany()
.Map(x => {
x.MapLeftKey("ListRuleId");
x.MapRightKey("ListId");
x.ToTable("ListRuleLists");
});
Before we go further, I woudl like to clarify the result I am looking for:
When someone deletes a List I want the cascade delete constraints to automatically delete the relationships associated with the List in the ListRule table as well as the many to many relationships between ListRule and ListRuleLists tables.
When I try to Update-Database, I get a error saying "FOREIGN KEY constraint 'FK_ListRuleLists_Lists_ListId' on table 'ListRuleLists' may cause cycles or multiple cascade paths." I understand why this is and currently one way to solve it is by adding a WillCascadeOnDelete(false) to the first model binding above.
But this creates an undesired side effect. Let's assume that there is one List with one ListRule with no many-to-many relationships in the ListRuleList table. If a user deletes a List, the ListRule will now be orphaned because of the WillCascadeOnDelete(false).
How can I go about adding the WillCascadeOnDelete(false) to the RightKey side of the second binding instead? In other words, I want to remove the cascade delete from the foreign key constraint "FK_ListRuleLists_Lists_ListId".
Is it possible? If not, are there any other ways to solve this issue?

Related

SQL Server CE identifies a cyclical reference with Entity Framework Code First but SQL Server 2008 does not

I am working on an Entity Framework Code First project that has a fairly complex Data Model which deploys absolutely fine on SQL Server 2008.
However when creating an SQL Server CE database for some local End-To-End testing I get the following error message when EF creates the database:
System.Data.SqlServerCe.SqlCeException: The referential relationship will result in a cyclical reference that is not allowed. [ Constraint name = FK_Sites_Persons_PersonId ].
I have disabled the ManyToManyCascadeDeleteConvention in my DataContext model creation method, so that isn't the cause of the problem. The trouble I have is that the relationship in question looks fine in the SQL Server 2008 database- it appears to be a normal foreign key from what I can tell and I can't see anything flowing back in the other direction, although it is not impossible that there is a longer-path circular reference. I don't know why CE would fail and 2008 would succeed.
It turns out the problem was very simply solved- although I had disabled ManyToManyCascadeDeleteConvention I also needed to disable the OneToManyCascadeDeleteConvention to avoid the circular reference problem.
You might also consider explicitly defining the cascading updates and deletes rather than disabling them globally. Assume a model:
namespace Models
{
public class Parent
{
public Parent() { this.Children = new HashSet<Child>(); }
public int id { get; set; }
public string description { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int id { get; set; }
public string description { get; set; }
public Parent Parent { get; set; }
}
}
Override the OnModelCreating in your context and use the fluent api to specify the cascade options for a given relationship.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().HasMany<Child>(p => p.Children).WithRequired(c => c.Parent).WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Of course this is a simple example, but you can apply the same principle to your down-level entities and specifically exclude the cascaded delete that causes the circular reference.

How to make a relationship be required in EF code first

I have a EF Code First One-To-Many relationship. It's essentially a Parent/Child relations as the child can't exist without the parent.
public class Parent
{
[Key]
public Guid Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
public virtual ICollection<OtherChild> OtherChildren { get; set; }
}
public class Child
{
[Key]
public Guid Id { get; set; }
public virtual Parent Parent { get; set; }
}
So I wasn't sure how I could have the child be required to have a Parent so I tried putting a [Required] attribute on it. That gave me the error:
- InnerException {"Introducing FOREIGN KEY constraint 'Child_Parent' on table 'Child'
may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE
NO ACTION, or modify other FOREIGN KEY constraints.\r\nCould not create constraint.
See previous errors."}
System.Exception {System.Data.SqlClient.SqlException}
Ok I'm not sure how he could have multiple cascading parts.
The parent also has other child objects and those child objects share a many-to-many relationship with the original child object but it shouldn't require a cascade delete.
I guess I'm doing this wrong but what is the proper way to do this.
PS. When I have a child require a parent should I make the foreign key a part of the primary key?
You can disable cascading delete for the relationship in Fluent API (it's not possible with data annotations):
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithRequired(c => c.Parent)
.WillCascadeOnDelete(false);
}
}
You must delete the children then as well in your code if you delete a parent.
You don't need to make the foreign key part of the primary key and I don't see a benefit in doing so. Your Guid key is already unique. It will help query performance though if you create an index on the foreign key column in the database.

How to model many-to-many relationships with a relationship entity in EF 4.1 Code First

Popular example: In the issue tracker JIRA, issues can be linked to other issues. The link itself has some data attached, in particular a type.
Example:
Issue A -> depends on -> Issue B
Issue B <- is depended on by <- Issue A
We are introducing the same kind of relationship for an entity in our C# ASP.NET MVC application using EF 4.1 CodeFirst, and I'm wondering how to best model this relationship?
Details:
There are some particularities about this situation:
A link has some data attached, so we can't simply model a many-to-many relationship between issues and issues. We rather have to introduce a new entity Link, which represents a relationship between two issues.
A link, by definition, links two instances of the same entity, it is a "two-to-many" relationship (a link has two issues, an issue can have many links).
The link is directed, which means, if Issue A depends on Issue B, then Issue B is depended on by Issue A.
We will certainly have a Link entity that looks like this:
public class Link
{
public int ID { get; set; }
public Issue IssueA { get; set; }
public Issue IssueB { get; set; }
public LinkType Type { get; set; }
}
The Issue class might look like this:
public class Issue
{
public int ID { get; set; }
public virtual ICollection<Link> Links { get; set; }
}
Currently there would be only one link type: dependency. So, the link type would look like this:
public class LinkType
{
public int ID { get; set; }
public string ForwardName { get; set; } // depends on
public string BackwardName { get; set; } // is depended on by
}
Now for the big question:
If I want EF to automatically manage Issue.Links, I have to tell it what Foreign key on the Link table to use. Either I use IssueA, or I use IssueB. I can't use both, can I?
Either I define:
modelBuilder.Entity<Issue>().HasMany(i => i.Links).WithRequired(l => l.IssueA);
or I define:
modelBuilder.Entity<Issue>().HasMany(i => i.Links).WithRequired(l => l.IssueB);
Possible approaches - I am curious about your feedback on whether some of them will lead to troubles, cannot be implemented, or whether any of these approaches can be regarded as "best practice":
Add two Collections to the Issue, ICollection<Link> OutgoingLinks, ICollection<Link> IncomingLinks. This way the collections can be maintained by EF, but from a business logic point of view they don't make much sense.
Only add one collection and configure EF 4.1 to add incoming and outgoing links to it, if that is possible.
Only add one collection and implement it on my own:
ICollection<Link> AllLinks { return _context.Links.Where(l => l.IssueA == this || l.IssueB == this).ToList(); }
The problem with this approach is that the domain entity executes data access tasks which is bad in terms of seperation of concerns.
Any other?
Option (1) is the way to go in my opinion, together with a readonly helper perhaps which combines the two collections:
public class Issue
{
public int ID { get; set; }
public virtual ICollection<Link> OutgoingLinks { get; set; }
public virtual ICollection<Link> InComingLinks { get; set; }
public IEnumerable<Link> Links // not mapped because readonly
{
get { return OutgoingLinks.Concat(InComingLinks); }
}
}
Option (2) isn't possible because you cannot map one navigation property to two different ends/navigation properties.

Entity Framework 4.1 Fluent mapping Foreign Key and the Foreign object with a string key

I am moving from an EDMX mapping to the EF 4.1 DbContext and Fluent mapping and I am wanting to map both a string foreign key and the foreign object using the fluent API. I have an Employee with an Optional Office. I would like both the OfficeId and the Office Object in the Employee class (This is all read only, and I do not need to be able to save these objects). Objects with int keys work fine, but I have tried several with string keys and get the same result - the OfficeId field populates, but the Office object comes back as null. Chekcking in SQL profiler the data is being queried, but the office object is not being populated.
public partial class Employee
{
public int Id { get; set; }
// snip irrelevant properties
public Office Office { get; set; } // this is (incorrectly) always null
public string OfficeId { get; set; }
public WorkGroup WorkGroup { get; set; } // this one with the int key is fine
public int? WorkGroupId { get; set; }
// snip more properties
}
public partial class Office
{
public string Id { get; set; }
public string Description { get; set; }
}
public partial class WorkGroup
{
public int Id { get; set; }
public string Code { get; set; }
}
After feedback from Ladislav below, I map it like this in the OnModelCreating
modelBuilder.Entity<Employee>().HasKey(d => d.Id).ToTable("Employee", "ExpertQuery");
modelBuilder.Entity<Office>().HasKey(d => d.Id).ToTable("Office", "ExpertQuery");
modelBuilder.Entity<WorkGroup>().HasKey(d => d.Id).ToTable("WorkGroup", "ExpertQuery");
modelBuilder.Entity<Employee>()
.HasOptional(a => a.Office)
.WithMany()
.Map(x => x.MapKey("OfficeId")); // this one does not work
modelBuilder.Entity<Employee>()
.HasOptional(e => e.WorkGroup)
.WithMany()
.HasForeignKey(e => e.WorkGroupId); // this one works fine
I assume there is some subtlety with string keys that I am missing ? I am querying it as follows :
var employees = expertEntities.Employees.Include("Office").Include("WorkGroup").Take(10).ToList();
If I omit the OfficeId field from Employee, and set up the mapping like this :
modelBuilder.Entity<Employee>()
.HasOptional(e => e.BusinessEntity)
.WithMany()
.Map(x => x.MapKey("OfficeId"));
Then the office object is populated, but I need the OfficeId field in the Employee object.
Well, I found the issue - it's a data issue - the primary key string values were space padded and the foreign key values were not (!). Although SQL joins the tables correctly (ignoring the padding) and fetches the correct data, it appears that EF will not correlate it back into the the correct objects as .NET is fussier than SQL about trailing blanks.
Your customized mapping just conflicts because of the fact that you have already introduced a OfficeId property of string type. See what happens if you remove the OfficeId property from your Employee definition, or change it to int type.
That is not correct mapping. If you have FK property you cannot use Map and MapKey. That is for scenarios where you don't have that property. Try this:
modelBuilder.Entity<Employee>()
.HasOptional(a => a.Office)
.WithMany()
.HasForeignKey(a => a.OfficeId);
Also first part of your mapping with mapping entities to table is most probably incorrect. Map is used for inheritance and entity splitting scenarios. You are looking for ToTable:
modelBuilder.Entity<Employee>().HasKey(d => d.Id).ToTable("ExpertQuery.Employee");
Also if your ExpertQuery is database schema and not part of table name it should look like:
modelBuilder.Entity<Employee>().HasKey(d => d.Id).ToTable("Employee", "ExpertQuery");

Can I specify a discriminator column with a table-per-type mapping?

I have a class hierarchy that I want to map across several tables using Entity Framework 4.1 Code First. It's like table-per-type (TPT) but I also want a discrimator column.
The hierarchy looks something like:
public class Event
{
public Guid Id { get; set; }
public string Code { get; set; } // discriminator
public DateTime Date { get; set; }
}
public class Party : Event
{
public int AttendeeCount { get; set; }
}
public class BirthdayParty : Party
{
public int Age { get; set; }
}
public class WeddingParty : Party
{
public string Surname { get; set; }
}
This is a pretty weak example but I hope it makes sense. There'll be an "Events" table, a "Parties" table and a table for each kind of party. However, the discriminator column ("Code") will have a known value for each kind of event, like "BIRTH" for birthday parties or "WEDDING" for wedding parties.
The idea is that if I query for just birthday parties on a given date, EF would know to add Code = 'BIRTH' to my query instead of doing a bunch of UNIONs and JOINs to work out which rows it needs.
I map my lowest-level classes like this:
var bd = modelBuilder.Entity<BirthdayParty>();
bd.ToTable("BirthdayParties");
bd.Property(p => p.Age).HasColumnName("BirthdayAge");
I now need to specify the discriminator value in there somehow. I've tried this:
modelBuilder.Entity<Event>().Map<BirthdayParty>(cfg =>
{
cfg.Requires("Code").HasValue("BIRTH");
});
... but that complains that I haven't specified the table name inside the call to Map. So I tried moving the ToTable call into there:
var bd = modelBuilder.Entity<BirthdayParty>();
bd.Property(p => p.Age).HasColumnName("BirthdayAge");
modelBuilder.Entity<Event>().Map<BirthdayParty>(cfg =>
{
cfg.Requires("Code").HasValue("BIRTH");
cfg.ToTable("BirthdayParties");
});
... and now it thinks I want a "Code" column in the "BirthdayParties" table, which is not correct. I've already told it that the "Code" column is in the "Events" table.
Is this even possible? Can I combine the use of a discriminator column with a table-per-type mapping?
Unfortunately this is not supported. Discriminator column can be used only in TPH. TPT differs entity types by mapped tables and it always produces those terrible queries. It could be nice feature so perhaps suggestion on Data UserVoice would make it implemented one day.
Update
There is already a suggestion on user voice for this titled "Discriminator column support in TPT inheritance".
I did an override on SaveChanges to accomplish something similar. I simply added an attribute onto the abstract class called Descriminator and set it based on the Concrete Class Name anytime something new is added.
public class MyContext : DbContext
{
public override int SaveChanges()
{
foreach (var item in ChangeTracker.Entries().Where(x=>x.Entity is MyAbstractClass && x.State == EntityState.Added))
{
((MyAbstractClass)item.Entity).Descriminator = item.Entity.GetType().Name;
}
return base.SaveChanges();
}
}