Removing Parent with one-to-many EntityFramework - c#

I have this class ( Simplified )
public class User {
public string Username { get; set; }
public virtual Section Section { get; set; }
}
public class Section {
public int DepartmentNumber { get; set; }
public virtual ICollection<User> DepartmentMembers { get; set; }
}
What i try to achive is to delete a section. All users related to this section shoulde get a null value. This dosent work now because REFERENCE constraint "FK_Users_Sections_Section".
What woulde be the correct way to do this? woulde i have to remove all users from this section before deleteing it? Is there a more elegant way to do this? Im using EntityFramework.
My fluent API for this field:
modelBuilder.Entity<User>()
.HasOptional(u => u.Section)
.WithMany(s => s.DepartmentMembers)
.Map(m =>
m.MapKey("Section")
)
.WillCascadeOnDelete(false);
The WillCascadeOnDelete i have tried it by setting to false, removing the line and also adding the line with no arguments.
I guess the solution is quit simple but i cant find any good explaination ( or mabye i dont understand the explainations i have been looking at. )

Although SQL Server has a SET NULL option for cascade deletes, which sets all foreign keys referencing a deleted record to null, Entity Framework does not use it.
You can either set this option on the constraint yourself in SQL Server, or you can let Entity Framework take care of it by updating loaded entities.
If you want EntityFramework to do it, you need to make sure the DepartmentMembers collection is loaded, so that all the User objects that will need to be updated are in the context.
Section s = context.Sections.Include(s => s.DepartmentMembers).First();
context.Delete(s);
context.SaveChanges();

Related

How to expose Foreign Key property to existing entity having navigational property using EF6 Code First

I have an entity which is already being used with an underlying database, and it was created with just the navigational property to an optional entity (1:0..1). So by default conventions, EF created a nullable foreign key column in the DB and gave it the "MyProp_Id" name with underscore, according to that convention.
Now, I wish to expose that foreign key as a property on the entity, because it will make certain scenarios easier for me. I don't want to rename/change the underlying foreign key column in the DB (the MyProp_Id one). In fact, there shouldn't be any underlying DB updates, I just want to expose that FK on the entity. A code sample to clarify:
public class MyEntityA
{
public long Id { get; set; }
//public long? MyOptionalEntityB_Id { get; set; } <== this is what I am trying to add
//public long? MyOptionalEntityBId { get; set; } <== this didn't work either
public MyEntityB MyOptionalEntityB { get; set; }
}
I've tried just simply adding the "MyOptionalEntity_Id" property as property on the entity, hoping that EF would "automagically" see that because the names are the same, it would just map and be happy. NO DICE.
Then I tried to name my property "MyOptionalEntityId" (no underscore), but still NO DICE.
Then I tried adding an explicit mapping configuration to say:
this.Property(p => p.MyOptionalEntityId).HasColumnName("MyOptionalEntity_Id");
NO DICE
Is there a way to do this? Is this clear and make sense?
Try adding foreign key attribute.
public long? MyOptionalEntityB_Id { get; set; }
[ForeignKey("MyOptionalEntityB_Id")]
public MyEntityB MyOptionalEntityB { get; set; }
Or using fluent api.
modelBuilder.Entity<MyEntityA >()
.HasOptional(x => x.MyOptionalEntityB)
.WithMany().HasForeignKey(x => x.MyOptionalEntityB_Id);
// ^^^ -> if MyEntityB has collection of MyEntityA, mention it

How to work with true self referencing entities in code first EF5?

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.

A relationship is in the Deleted state

When I am trying to clear a collection (calling .Clear) I get the following exception:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
The inner exception is:
A relationship from the 'User_Availability' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'User_Availability_Target' must also in the 'Deleted' state.
User looks like this:
....
ICollection<Availability> Availability { get; set; }
Availability looks like this:
int ID { get; set; }
User User { get; set; }
DateTime Start { get; set;
DateTime End { get; set; }
Configuration is as follows:
HasMany(x => x.Availability).WithRequired(x => x.User);
HasRequired(x => x.User).WithMany(x => x.Availability);
The code causing the problem is:
user.Availability.Clear();
I've looked at other alternatives such as using the DbSet to remove items, but I don't feel my code will be as clean. Is there a way to accomplish this by clearing the collection?
The only way that I'm aware of to make it work is defining the relationship as an identifying relationship. It would required to introduce the foreign key from Availability to User as a foreign key into your model...
public int ID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
...and make it part of the primary key:
modelBuilder.Entity<Availability>()
.HasKey(a => new { a.ID, a.UserID });
You can extend your mapping to include this foreign key (just to be explicit, it isn't required because EF will recognize it by convention):
modelBuilder.Entity<Availability>()
.HasRequired(a => a.User)
.WithMany(u => u.Availability)
.HasForeignKey(a => a.UserID);
(BTW: You need to configure the relationship only from one side. It is not required to have both these mappings in your question.)
Now you can clear the collection with user.Availability.Clear(); and the Availability entities will be deleted from the database.
There is one trick. You can delete entities without using special DbSet:
(this.dataContext as IObjectContextAdapter).ObjectContext.DeleteObject(entity);
Execute this for each item in Availability collection before clearing it. You don't need 'identifying relationships' for this way.
In case someone has the same problem using SQLite:
Unfortunately the accepted answer does not work with SQLite because SQLite does not support auto increment for composite keys.
You can also override the SaveChanges() Method in the Database context to delete the children:
//// Long Version
//var localChilds = this.SubCategories.Local.ToList();
//var deletedChilds = localChilds.Where(w => w.Category == null).ToList();
//foreach(var child in deletedChilds) {
// this.SubCategories.Remove(child);
//}
// Short in LINQ
this.SubCategories.Local
.Where(w => w.Category == null).ToList()
.ForEach(fe => this.SubCategories.Remove(fe));
#endregion
See this great Blogpost as my source (Unfortunately written in german).

Cascadable one-to-one, required:required relationship with EF

I have a Video class and a MediaContent class that are linked by a 1-1, required:required relationship: each Video must have exactly 1 associated MediaContent. Deleting a MediaContent object must result in the deletion of the associated Video object.
Using the fluent API, the relationship can be modeled as follows:
modelBuilder.Entity<Video.Video>()
.HasRequired(v => v.MediaContent).WithRequiredPrincipal(mc => mc.Video)
.WillCascadeOnDelete(true);
When adding a migration to reflect this change in the database, this is how the relationship gets transcribed in terms of foreign keys:
AddForeignKey("MediaContents", "MediaContentId", "Videos", "VideoId", cascadeDelete: true);
Updating the database, I get the following error:
Cascading foreign key 'FK_MediaContents_Videos_MediaContentId' cannot be created where the referencing column 'MediaContents.MediaContentId' is an identity column.
Dropping the WillCascadeOnDelete(true) property removes the error, but I'm not sure I understand why. Shouldn't the error appear whether or not cascading is turned on? The way I understand the problem, the error comes from the fact that the generation of VideoId and MediaContentId is handled by auto-increment (or by whatever the id generation strategy is), potentially contradicting the foreign key constraint. But I can't see what this has to do with delete-cascading...
What am I missing? More generally, how would you go about modeling a cascadable one-to-one, required:required relationship with EF?
I avoid the modelBuilder cruft approach and use simple POCOs and attributes generally - which you can use to accomplish your goals like so:
public class Video
{
public int Id { get; set; }
// Adding this doesn't change the db/schema, but it is enforced in code if
// you try to add a Video without a MediaContent.
[Required]
public MediaContent MediaContent { get; set; }
}
public class MediaContent
{
[ForeignKey("Video")]
public int Id { get; set; }
public Video Video { get; set;}
}

Mapping references to companion objects with fluent-nhibernate

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.

Categories