Fluent NHibernate cascade delete not working - c#

I've got a simple phone directory app using Fluent NHibernate 1.1. In the app, a "Person" object has many "PhoneNumber" objects. I'm trying to delete a Person and I want to cascade deletes to PhoneNumbers. I set a convention of DefaultCascade.All() after reading this answer. However, attempting to delete the parent object still throws an exception--it appears that NHibernate is trying to update the child table to set the parent ID to null instead of just deleting the record:
{"could not delete collection: [Person.PhoneNumbers#473][SQL: UPDATE phone_numbers SET person_id = null WHERE person_id = #p0]"}
InnerException:
{"Cannot insert the value NULL into column 'person_id', table 'directory.dbo.phone_numbers'; column does not allow nulls. UPDATE fails.\r\nThe statement has been terminated."}
My Fluent config is:
public static ISessionFactory CreateSessionFactory() {
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["activeConnStr"]].ConnectionString))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Person>()
.Conventions.Add(DefaultCascade.All())
)
.BuildSessionFactory();
}
The parent class is:
public class Person {
public Person() {
PhoneNumbers = new List<PhoneNumber>();
EmailAddresses = new List<string>();
}
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Company { get; set; }
public virtual IList<PhoneNumber> PhoneNumbers { get; set; }
public virtual IList<string> EmailAddresses { get; set; }
}
The child class (PhoneNumber) is:
public class PhoneNumber {
public virtual string Number { get; set; }
public virtual PhoneNumberType NumberType { get; set; }
public virtual Person Person { get; set; }
}
My code to delete a person is:
public static void DeletePerson(int id) {
using (var session = Dalc.Instance.SessionFactory.OpenSession()) {
using (var trans = session.BeginTransaction()) {
session.Delete(session.Load<Person>(id));
trans.Commit();
}
}
}
What am I doing wrong?

I'm not sure about configuring the Fluent part, but I recently had the same problem with ActiveRecord.
You need to set your association, on the Person side, as Inverse = true.
From looking at the Getting Started documentation...
I belive, you need to set this when defining your HasMany relationship in Person. It should look something like this:
public PersonMap()
{
//<...SNIP...>
HasMany(x => x.PhoneNumbers)
.Inverse();
//<...SNIP...>
}

It works; Here is what each cascade option means:
none - do not do any cascades, let the users handles them by themselves.
save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
delete - when the object is deleted, delete all the objects in the association.
delete-orphan - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
all-delete-orphan - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id);
Map(x => x.Name);
HasMany<PhoneNumber>(x => x.PhoneNumberList)
.KeyColumn("PersonId")
.Cascade.All()
.Inverse().LazyLoad();
}
}

Related

Entity Framework Fluent API Cannot Insert one-to-one

I am trying to insert an object into a database table with Entity Framework and using code first (fluent api). Whilst doing this I keep running into one of the following errors:
1) InvalidOperationException: A dependent property in a
ReferentialConstraint is mapped to a store-generated column. Column:
'Id'
2) Cannot insert value into identity column with IDENTITY_INSERT set
to OFF
My relationship is a one-to-one however perhaps I can rework or structure the database to accomplish what I am wanting. I have also thought about utilizing a one to zero or zone even though the other object will always be required.
So I have the following database tables mapped into these C# objects (with virtual for the mapping):
public class test
{
[Key]
public long Id { get; set; }
public DateTime ResultDate { get; set; }
public virtual test_additional test_additional { get; set; }
public virtual test_status test_status { get; set; }
}
public class test_additional
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long TestId { get; set; } //Foreign Key to test
...
public virtual test test { get; set; }
}
public class test_status {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long TestId { get; set; } //Foreign Key to Test
public long TestFormId { get; set; } //this is the object I want to insert, Foreign key to the Primary key of test_form
...
public virtual test test { get; set; }
public virtual test_form test_form { get; set; } //object mapping
}
public class test_form {
[Key]
public long Id { get; set; } //Primary Key
public string FileName { get; set; }
public virtual test_status test_status { get; set; }
}
So some pretty simple objects, I've stripped members/columns that are necessary for the functionality for ease of readability.
So there are test objects that have an optional test_additional or test_status .
These are generated with a one to zero-or-one relationship. Which are working fine and I have the relationship defined as:
modelBuilder.Entity<test>()
.HasOptional(e => e.test_additional)
.WithRequired(e =>e.test);
modelBuilder.Entity<test>()
.HasOptional(e => e.test_status)
.WithRequired(e => e.test);
Now the entity I am having trouble with is the test_form, if a test_status is defined there should always be a test_form associated with that. I currently have a relationship defined as:
modelBuilder.Entity<test_form>()
.HasRequired(e => e.test_status)
.WithRequiredDependent(e => e.test_form);
In addition I have tried appending this config:
modelBuilder.Entity<test_status>()
.HasKey(e => e.TestFormId);
--
Here is a simple implementation of inserting this object in the database:
try {
test UserTest = new test { ResultDate = DateTime.Now; }
UOW.test.Insert(UserTest);
UOW.Save();
test_additional ta = new test_additional { TestId = UserTest.Id; }
test_form tf = new test_form { FileName = "Testing.pdf"; }
UOW.test_additional.Insert( ta );
UOW.test_form.Insert( tf );
UOW.Save(); //This is where it will throw that error.
test_status status = new test_status {
TestId = UserTest.Id;
TestFormId = tf.Id;
}
UOW.test_status.Insert( status );
UOW.Save();
} catch {
throw;
}
--
I have used BreakPoints before the Unit of Work saves and I can confirm that the Id in the test_form object is the default of long which is 0. So I am not setting the Identity Column explicitly. Upon removing of test_form (in the implemented method) I can insert into the test_additional category and save with no issue.
So my question is really... are my entity relationships defined correctly? Would it be smarter to use an additional One to Zero-or-One for the test_form object? Why can I not insert this simple object into my database?
I have also thought about defining the virtual test_form object in test_status as an ICollection, then I could use .HasMany(e => e.test_form).HasForeignKey(e => e.TestFormId); so it would bind to the Foreign Key even though I would only be using 1 item for the test_status.
Opinions? Am I close?
Thanks again for taking the time to read my question!
i had your problem. just do delete your database and migration files. after do it add the new migration to create the new database.

How to create a table with two foreign keys pointing to the same table using Entity Framework Code First

I have the following classes, which I'm trying to store in a database using Entity Framework 6 code first.
Person:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<WorkItem> WorkItems { get; set; }
}
WorkItem:
public class WorkItem
{
public Guid Id { get; set; }
public string Description { get; set; }
public Person Creator { get; set; }
}
As you can see each person can have a number of task. But(!) the task also has a creator that is a person.
I expect the workitem table created by using entity framework's add-migration to have two foreign keys. One so that the workitem can bleong to the WorkItems collection of Person, and one that points to the creator of the Workitem. Like the picture below shows:
This doesn't seem to be such a weird scenario, but it's causing me loads of problems.
If you just try to create database tables using add-migration the WorkItem is created in the following way:
CreateTable(
"dbo.WorkItems",
c => new
{
Id = c.Guid(nullable: false),
Description = c.String(),
Creator_Id = c.Guid(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.People", t => t.Creator_Id)
.Index(t => t.Creator_Id);
As you can see there is nothing that makes the Person an owner of its work items here. If I remove the Creator property it works as expected. There seems to be a problem referencing a class when that class is the owner.
Why doesn't this just work out of the box and what is the best way to fix it? Navigation property? Fluent API?
Configure your entities using this configuration in your DbContext.OnModelCreating (or better yet, add separate entityconfigurations):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasMany(p => p.WorkItems).WithMany();
modelBuilder.Entity<WorkItem>().HasRequired(t => t.Creator).WithMany().WillCascadeOnDelete(false);
}
This will create a table for Person and another for WorkItem and another one to support the many-to-many relationship between Person and WorkItem. It will also create a separate FK in WorkItem to reference Person:
I couldn't understand your question entirely. I understand that every person can have multiple tasks, but I'm not sure whether a task can be assigned to only one person or multiple people. If you want a one-to-many relationship only, use this configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasMany(p => p.WorkItems).WithOptional();
modelBuilder.Entity<WorkItem>().HasRequired(t => t.Creator).WithMany().WillCascadeOnDelete(false);
}
This only creates the two tables and in WorkItem two FK-s to reference the Person table; one for the Creator and one so that EF can wire up the references in Person to the WorkItems:
This looks like as one to many relationship, so your Tasks property should be ICollection<WorkItem>, you need to update your both classes.
Your Person class would be like:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<WorkItem> Tasks { get; set; }
}
and you would also need to modify your WorkItem class to have reference to the Person instance who created the WorkItem, so WorkItem class would be like:
public class WorkItem
{
public Guid Id { get; set; }
public string Description { get; set; }
[ForeignKey("Person")]
public Guid WorkItemCreatorId{ get; set; }
public Person Creator { get; set; }
}
Now this should generate the correct sql in migrations.
For more on Entity Framework one-to-many relationships, please have a look here
Hope it works for you.

"Adding a relationship with an entity which is in the Deleted state is not allowed" occurs when trying to delete "many" set in optional relationship

I have a one-to-many relationship where I am trying to delete a set of instances on the "many" side, but keep getting the exception "Adding a relationship with an entity which is in the Deleted state is not allowed". This is Entity Framework 6.1.1.
The relationship is one-to-many from Teacher to Course. The two classes are defined as:
[Table("Course")]
public partial class Course {
public int Id { get; set; }
public int? TeacherId { get; set; }
public virtual Teacher Teacher { get; set; }
}
[Table("Teacher")]
public partial class Teacher
{
public Teacher()
{
Course = new HashSet<Course>();
}
public int Id { get; set; }
[Required]
[StringLength(50)]
public string TeacherName { get; set; }
public virtual ICollection<Course> Course { get; set; }
}
The code that tries to delete the courses, is part of an import: A set of courses is coming in, and the courses that are in the database but not part of the incoming courses, should be deleted from the database. (In addition, the courses that are part of the incoming set but not in the database, should be created, but this seems to work).
var existingCourses = ctx.Courses.ToList();
var toCreate = incomingCourses.Where(x => !existingCourses.Contains(x)).ToList();
var coursesToDelete = existingCourses.Where(x => !incomingCourses.Contains(x)).ToList();
ctx.Courses.RemoveRange(coursesToDelete);
ctx.SaveChanges(); // The exception occurs here
The incoming set of courses are parsed from an XML file to a DTO. Before comparing them to the existing courses, they are placed in a list as:
var incomingCourses = incomingDtos.Select(x => new Course
{
Teacher = new Teacher { TeacherName = x.TeacherNameFromXml }
}.ToList();
There are other properties on the Course entity that identifies the course, but I have not shown them here as I suppose they are irrelevant.
When debugging, I noticed that the Teacher property of the Courses that are being deleted are non-empty before the call to RemoveRange() but null afterwards.. So it seems that there is some kind of cascade delete taking place.
I have tried to remove all cascade deletes via my DbContext and also specifying the relationship there. This changes nothing.
public class MyDbContext : DbContext {
public MyDbContext() {}
public MyDbContext(string connectionString) {
Database.Connection.ConnectionString = connectionString;
}
public virtual DbSet<Teacher> Teacher { get; set; }
public virtual DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Teacher>()
.HasMany(x => x.Course)
.WithOptional(x => x.Teacher)
.HasForeignKey(x => x.TeacherId)
.WillCascadeOnDelete(false);
}
}
The comment from #LukasKabrt pointed me in the right direction. Instead of comparing the Course objects directly, I convert the existing courses to DTOs "temporarily" inside the LINQ and compare DTOs. Now I am able to delete all from coursesToDelete
var coursesToDelete = (
from e in existing
let dto = new CourseDto(e)
where !incomingDtos.Contains(dto)
select e).ToList();

Fluent NHibernate Relation Without Foreign Key

I'm trying so simplify my problem here, but basically I'm trying to map 2 entities however i don't have a Foreign Key in the database set, since the column could be null. When I try to do an insert on the parent, I'm getting the following error:
object references an unsaved transient instance - save the transient
instance before flushing or set cascade action for the property to
something that would make it autosave.
This is what I have so far:
My entities
public class DocumentDraft
{
public virtual Guid Id { get; set; }
public virtual string Subject { get; set; }
public virtual string ReferenceNo { get; set;}
public virtual DocumentType DocumentType { get; set; }
}
public class DocumentType
{
public virtual short Id { get; set; }
public virtual string Description { get; set; }
}
Mapping
public class DocumentDraftMap : ClassMap<DocumentDraft>
{
public DocumentDraft()
{
// other mappings ...
References(x => x.DocumentType)
.Columns("DocumentTypeId")
.Nullable()
.Not.LazyLoad()
.NotFound.Ignore(); // <-- added this since the value could be null and it throws an error
}
}
I tried specifying Cascade.None() in the mapping, but I'm getting the same result. Basically what happens is that a null value is attempted at being inserted in the DocumentType, and I don't want this (I want to insert null in the parent table, but I don't want to touch the child tables at all, I don't want this to cascade).
I've also tried: .Not.Insert(), but that didn't work either.
I'd appreciate it if someone could help me out on this one.
I guess the property DocumentType is not really null when saving.
It seems there is an instance and without Cascade.All() on the reference it can not be saved.

Entity Framework DbUpdateException when storing m:n relationship

I'm having troubles with creating and/or storing m:n relationship with EF 4.3 Code first
So the first entity Publication is defined as with some other internal scalar properties:
public class Publication : IDataErrorInfo{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PublicationId { get; set; }
[InverseProperty("Publications")]
public virtual ICollection<Group> Groups { get; set; }
and the other class in the same way:
public class Group : IDataErrorInfo {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupId { get; set; }
[InverseProperty("Groups")]
public ICollection<Publication> Publications { get; set; }
which according to numerous articles should be fine.
There are several problems I occur. AT first:
If I create a new publication and assert it some Groups. All is stored to the db. But then I restart the program, the same particular publication has ICollection set to null. Therefore the information about relationship with Group has been deleted.I don't know why :(
When I try to update exisiting Publication entry with Group relationship, the DBUpdateException is thrown with the following text:
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
In the inner exception is the same, and in the inner exception of this one is the following:
Violation of PRIMARY KEY constraint 'PK_Publicat_3AF5D6A10AD2A005'. Cannot insert duplicate key in object 'dbo.PublicationGroups'.
The statement has been terminated.
I'm asserting the the new values of the Publication as follows:
var entry = db.Publications.First(a => a.PublicationId == publKey);
entry.Groups = db.Groups.
Where(a => groupKeys.Contains(a.GroupId)).
Select(b => b).
ToList();
where publKey is the key of the edited entity and groupKeys is the List of GroupId which should be the publication be related to.
after calling db.SaveContext() the exception is thrown
This topic has been covered by numerous articles, but I didn't find any solution. All of the examples are using the same code, but apparently I'm missing something. I'm using SQL Ce 4.0 as the persistance data storage.
Thank you guys for answer, I'm dealing with it since yesterday, but don't why this happens
Are you calling .Save after you've added the groups to the database? Otherwise when you try to add the groups to the publication, they won't actually be in the DbSet yet. The following code works for me - perhaps you could clarify how it differs from yours?
public class Publication
{
public int PublicationId { get; set; }
public string PublicationName { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public string GroupName { get; set; }
public ICollection<Publication> Publications { get; set; }
}
public class Context : DbContext
{
public DbSet<Publication> Publications { get; set; }
public DbSet<Group> Groups { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
Context context = new Context();
// Only add the groups if it's a new database
if (!context.Groups.Any())
{
context.Groups.Add(new Group { GroupName = "Group 1" });
context.Groups.Add(new Group { GroupName = "Group 2" });
context.SaveChanges();
}
if (context.Publications.Any())
{
Console.WriteLine("At startup, P1 is in groups " + String.Join(", ", context.Publications.First().Groups.Select(g => g.GroupName)));
}
// Add publication
Publication p;
p = new Publication();
p.Groups = context.Groups.ToList(); // Add to all existing groups
context.Publications.Add(p);
context.SaveChanges();
Console.WriteLine("P1 is in groups " + String.Join(", ", context.Publications.First().Groups.Select(g => g.GroupName)));
}
}
(Code Updated to use SqlCe provider)

Categories

Resources