Deletion of entire entity graph, including relationships, using EF Code First - entity-framework

I have classes that are structured like the following:
public class Forecast
{
[Key]
[ForeignKey("Stop")]
public string Abbreviation { get; set; }
public virtual Stop Stop { get; set; }
public virtual List<Direction> Directions { get; set; }
}
public class Direction
{
public int DirectionId { get; set;}
public string Abbreviation { get; set;}
public virtual Forecast Forecast { get; set;}
public virtual List<Transport> Transports { get; set;}
}
public class Transport
{
public int TransportId { get; set; }
public int DirectionId { get; set;}
public virtual Direction Direction { get; set;}
}
public partial class Stop
{
[Key]
public string Abbreviation { get; set; }
public virtual Forecast Forecast { get; set; }
}
I developed these classes and used EF Code First 4.1 to generate the database. CF does appear to properly create all of the primary and foreign key relationships between the classes within the database (MSSQL).
My problem is when I want to delete a Forecast. I thought I do could something like the following:
using (MyContext ctxt = new MyContext())
{
// get a forecast, somehow, not really important
// The one assumption is I'm absolutely sure it's
// Abbreviation key already exists in the database
// and the list of Forecasts.
Forecast f;
ctxt.Forecasts.Remove(f);
}
This deletes the top-level object from the database just fine. However, all of its child objects - all of the directions and transports - remain in the database and become orphaned (their key relationship column gets set to null. I expect that but I DON'T know why they're not just deleted). I have resorted to recursing down the object graph and calling Remove on every object from its appropriate DbSet in ctxt, but that seems like... the wrong way to do it.
What am I missing here?
Why can't I just say
ctxt.Forecasts.Remove(f);
and be done with it?
Edit:
#Ladislav gave me the right answer - I
needed to add [Required] to the
Abbreviation property on Direction.
However, I am still forced to actually
load the child entities for this to
work - doing something as simple as
Direction d = f.Directions[0];
will cause the delete to actually
delete the child entities. I'm well
aware that this is due to lazy
loading. I thought the point of the
FK relationship and ON CASCADE DELETE
was that you wouldn't have to actually
load the entities to delete them?
Again I seem to be missing something simple.

#Eranga is right that this is done by ON DELETE CASCADE setting on relation in the database BUT you are using code first approach and EF creates database for you so the problem here is that your model is not correctly defined because EF didn't create cascading rule for you.
Why? Because of this:
public class Direction
{
public int DirectionId { get; set; }
public string Abbreviation { get; set; }
public virtual Forecast Forecast { get; set; }
public virtual List<Transport> Transports { get; set; }
}
Abbreviation is FK property and it is nullable! So EF looks at your model and it sees that you defined Direction entity which can have Abbreviation set to null and because of that it can exists orphaned. Change it to:
public class Direction
{
public int DirectionId { get; set; }
[Required]
public string Abbreviation { get; set; }
public virtual Forecast Forecast { get; set; }
public virtual List<Transport> Transports { get; set; }
}
and removing Forecast will delete all related Direction instances and Transport instances. Stop is different story because it is parent entity to Forecast so it will never be removed with Forecast.
Edit:
One more point - you don't want to add ON DELETE CASCADE to your relations manually because EF have to know about enabled cascade deletes. EF use this information in case where you have related entities loaded.
If you place the rule manually into the database you must use fluent mapping and tell EF about this rule as well. Once you force cascade delete in fluent api you don't need to make it manually in the database - it will be created automatically during database recreation.

You can easily achieve this by setting ON DELETE CASCADE when you create foreign keys in the Database.

Related

One-to-one mapping in multiple tables

I'm trying to solve one puzzle, but with no luck so far.
I have an article (or blog post) and comment entities, they both have content. In order to support lazy loading for content (there is no need to load the content when I need to display a list of articles or comments) I decided to move content to separate table and organize one-to-one mapping. Here is an example of what I think:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to BlogArticle
}
public class Comment {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to comment
}
<...>
From first look it seems ok: I can create blog articles, comments and attach content (at first I insert content, obviously). Update works as well. However, deletion doesn't work: when I delete blog article or comment, content is not deleted (but I want to delete it when blog article or comment are deleted, not opposite).
From what I understand my biggest issue because of relationship direction: in my case, Content entity is principal end and BlogArticle and Comment are dependent ends. In order to solve the puzzle, I need to change principal/dependent relationship. Again, from what I understand in order to change relationship direction I need to have a foreign key in Content entity and use fluent API to describe who is parent (principal) and who is child (dependent) in one-to-one relationship. Since many tables (there might be other entities with content property) are pointing to Content table, it doesn't seem very easy. Am I correct in my understanding?
One possible solution I could imagine is to create multiple foreign keys in Content table and point to each related table:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// foreign keys
public int BlogArticleID { get; set; }
public int CommentID { get; set; }
public int WebWidgetID { get; set; }
// other foreign keys if necessary
}
probably, foreign keys must be nullable (because only single foreign key is used at once). Then use Entity Framework fluent API to describe relationship directions and organize cascade delete. For me it looks ugly, but I have no other ideas.
My question: is my proposed solution good/reliable? Are there other options I can look at?
Thanks in advance!
All your thoughts are correct. And your proposed solution is the only way with traditional relational design. The drawback of course is the need of multiple mutually exclusive nullable FKs.
The other options I see are as follows:
(1) Using EF inheritance for the entities holding Content. e.g.
public abstract class EntityWithContent
{
[Key]
public int ID { get; set; }
public virtual Content Text { get; set; }
}
public class BlogArticle : EntityWithContent
{
// other specific properties
}
public class Comment : EntityWithContent
{
// other specific properties
}
and configured one-to-one relationship between Content (dependent) and EntityWithContent (principal) using either shared PK association or FK association.
But since EF Core currently supports only TPH strategy (i.e. all the derived entities share one and the same table with union of all fields), I won't recommend it.
(2) Making Content owned type.
This is closer to the intent, but unfortunately EF Core currently always loads the owned entity data along with the owner data (even if they are configured to be provided by different database tables), which is against your original goal, so I won't suggest that either.
(3) Using table splitting feature.
If the main goal is simple to support controlled (lazy/eager/explicit) loading and the Content is always required, then this might be the best solution so far.
It would require a bit more configuration, but at the end it will give you the original table design (single table per entity) with the desired loading behavior:
Model:
public abstract class Content
{
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle
{
[Key]
public int ID { get; set; }
public virtual BlogArticleContent Text { get; set; }
// other properties related to BlogArticle
}
public class BlogArticleContent : Content
{
}
public class Comment
{
[Key]
public int ID { get; set; }
public virtual CommentContent Text { get; set; }
// other properties related to comment
}
public class CommentContent : Content
{
}
Note that here Content class is not part of EF inheritance hierarchy, but simple base class with the common properties (abstract modifier is not strongly necessary). The actual derived classes might or might not define their own properties.
Configuration:
modelBuilder.Entity<BlogArticle>().ToTable("BlogArticles");
modelBuilder.Entity<BlogArticle>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<BlogArticleContent>(e => e.ID);
modelBuilder.Entity<BlogArticleContent>().ToTable("BlogArticles");
modelBuilder.Entity<Comment>().ToTable("Comments");
modelBuilder.Entity<Comment>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<CommentContent>(e => e.ID);
modelBuilder.Entity<CommentContent>().ToTable("Comments");

EF creating an unwanted field in database

I've hit a snag while building a .net mvc site. I have 2 related objects and am struggling with properly linking them. Specifically:
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostCode { get; set; }
[ForeignKey("AddressCategory")] // <-- EF adds field to below object's table
public int AddressCategoryId { get; set; }
public virtual AddressCategory AddressCategory { get; set; }
}
public class AddressCategory
{
public int AddressCategoryId { get; set; }
public string Description { get; set; }
}
Adding the [ForeignKey] data annotation to the Address object results in EF adding an Address_AddressId column to the AddressCategory table, which I don't want (or need) to happen.
I've tried to omit the ForeignKey attribute, but then I run into other errors because .net can't link the tables (e.g. Unknown column 'Extent1.AddressId' in 'field list'). Additionally, I wouldn't be able to use:
var addresses = db.Addresses.Include(l => l.AddressCategory);
Is there any way to link the 2 tables without EF adding an additional column to the AddressCategory table?
Thank you to #cloudikka for responding. After much trial-and-error I seem to have gotten it to work simply by omitting any ForeignKey reference from either object. I let EF rebuild the database and perform all scaffolding (CRUD forms) and they have been created perfectly.
My take-away is that foreign key attributes should be used for parent-child relationships, but not for look-up tables. I clearly have much to learn about asp.net mvc!

Entity Framework customizing Foreign Key relationships

I am using Entity Framework 6. I have created my data models Code First and the database is up and running, everything is working just fine. Below is a pseudo representation of my data. I'll continue the explanation below the code snippet.
public class ObjectTypeA
{
public ObjectTypeA()
{
Bs = new HashSet<ObjectTypeB>()
Cs = new HashSet<ObjectTypeC>()
}
public ICollection<ObjectTypeB> Bs { get; private set;}
public ICollection<ObjectTypeC> Cs { get; private set;}
}
public class ObjectTypeB
{
[ForeignKey("ObjectA")]
public Guid ObjectTypeAId { get; set; }
public virtual ObjectTypeA ObjectA { get; set;}
}
public class ObjectTypeC
{
[ForeignKey("ObjectA")]
public Guid ObjectTypeAId { get; set; }
public virtual ObjectTypeA ObjectA { get; set;}
}
So, as I mentioned, everything is working fine, including cascade delete, which is on by default. What I would like to be able to do is this:
Delete ObjectTypeA is allowed if there are ObjectTypeBs holding a key to it, but not if there are ObjectTypeCs holding keys to it.
If Cs.Count > 0, ObjectTypeA cannot be deleted.
Can this be done in the markup, does it need to be done in Fluent?
Since cascade delete is on, would a different approach be better where I turn it off and then enable it as an when desired? If so, how?
I looked at using nullable ForeignKeys but that doesn't appear to be the functionality I am looking for.

EF 5 code first optional one to one mapping accessing foreign key fields on the model

This question is basically a repeat of this question regarding EF4 CTP but specific to EF 5.
I have a POCOs set up such that
public class ClassPrinciple
{
public int ClassPrincipleID { get; set; }
public virtual ClassDependent ClassDependent{ get; set; }
}
and
public class ClassDependent
{
public int ClassDependentID { get; set; }
public virtual ClassPrinciple ClassPrinciple{ get; set; }
}
in my model builder I create the optional one to one mapping like this
modelBuilder.Entity<ClassPrinciple>().HasOptional(p => p.ClassDependent)
.WithOptionalDependent(s => s.ClassPrinciple);
this creates, on the ClassPrinciples table a column called ClassDependent_ClassDependentID . I would like to be able to reference the data in this column through a property on the ClassPrinciple model but I seem unable to do so. The web page I linked to at the top of this question states:
EF in general only supports exposing FK properties on your entities in
one:many relationships (unless the FK is also the PK). This is
somewhat artificial but a side effect of EF not supporting non-PK
unique constraints. We are working on support for unique constraints
for EF at the moment but it won't be there in our first RTM of Code
First.
Sorry not to have a better answer as there really isn't a workaround
at this stage.
Is this still the case or is there a way to resolve this. I have tried fluent api map to column and data annotations in all sorts of combinations without success.
use this code :
public class ClassPrinciple
{
public int ClassPrincipleID { get; set; }
public int ClassDependentId { get; set; }
[ForeignKey("ClassDependentId")]
[InverseProperty("ClassPrinciple")]
public virtual ClassDependent ClassDependent{ get; set; }
}
public class ClassDependent
{
public int ClassDependentID { get; set; }
[InverseProperty("ClassDependent")]
public virtual ClassPrinciple ClassPrinciple{ get; set; }
}

Entity framework two-way relationship not loading includes

This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}