Fluent NHibernate: ManyToMany Self-referencing mapping - c#

I need help in creating the correct fluent nh mapping for this kind of scenario:
A category can be a child of one or more categories. Thus, resulting to this entity:
public class Category : Entity, IAggregateRoot
{
[EntitySignature]
public virtual string Name { get; set; }
public virtual IList<Category> Parents { get; set; }
public virtual IList<Category> Children { get; set; }
public virtual IList<ProductCategory> Products { get; set; }
public Category()
{
Parents = new List<Category>();
Children = new List<Category>();
Products = new List<ProductCategory>();
}
public virtual void AddCategoryAsParent(Category parent)
{
if (parent != this && !parent.Parents.Contains(this) && !Parents.Contains(parent))
{
Parents.Add(parent);
parent.AddCategoryAsChild(this);
}
}
public virtual void RemoveCategoryAsParent(Category parent)
{
if (Parents.Contains(parent))
{
Parents.Remove(parent);
parent.RemoveCategoryAsChild(this);
}
}
public virtual void AddCategoryAsChild(Category child)
{
if(child != this && !child.Children.Contains(this) && !Children.Contains(child))
{
Children.Add(child);
child.AddCategoryAsParent(this);
}
}
public virtual void RemoveCategoryAsChild(Category child)
{
if(Children.Contains(child))
{
Children.Remove(child);
child.RemoveCategoryAsParent(this);
}
}
}
My initial mapping is this:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(p => p.Id).GeneratedBy.Identity();
HasManyToMany(x => x.Parents)
.Table("CategoryParents")
.ParentKeyColumn("CategoryId")
.ChildKeyColumn("ParentCategoryId")
.Cascade.SaveUpdate()
.LazyLoad()
.AsBag();
HasManyToMany(x => x.Children)
.Table("CategoryParents")
.ParentKeyColumn("ParentCategoryId")
.ChildKeyColumn("CategoryId")
.Cascade.SaveUpdate()
.Inverse()
.LazyLoad()
.AsBag();
}
}
The problem with this mapping is whenever I remove a category as a parent or as a child of another category, the resulting SQL statement is this:
NHibernate: DELETE FROM CategoryParents WHERE CategoryId = #p0;#p0 = 2
NHibernate: INSERT INTO CategoryParents (CategoryId, ParentCategoryId) VALUES (#p0, #p1);#p0 = 2, #p1 = 3
It deletes all the mapping first, then insert the remaining mapping. The proper way is just delete the category parent mapping which this kind of statement:
DELETE FROM CategoryParents WHERE CategoryId = #p0 AND ParentCategoryId = #p1;#p0 = 2, #p1=1
Any ideas?

after looking at your mappings I think you are wanting to change your Cascade options. Here is an article which outlines a parent - child relationship between entities. Although it comes from the point of view of having orphaned entities I think you will find the blog helpful. Good luck...
http://ayende.com/Blog/archive/2006/12/02/nhibernatecascadesthedifferentbetweenallalldeleteorphansandsaveupdate.aspx

Related

How to delete a foreign key record with Entity Framework?

I've got two models - Parent and Child. When I delete the Parent, I was hoping the Child would get deleted as it has a ForeignKey attribute however that's not the case. I could add logic within the Parent repository's Delete method as shown in the commented out code below but I was wondering if that's required of if there's a simpler way to go about this?
public record Parent
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
}
public record Child
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Parent")]
public Guid ParentId { get; set; }
public string Name { get; set;}
}
public void Delete(Guid id)
{
/*
var children = _dbContext.Childs.Where(c => c.ParentId == id);
if (children != null)
{
foreach (var child in children)
{
_dbContext.Childs.Remove(child);
}
}
*/
var parent = _dbContext.Parents.FirstOrDefault(p => p.Id == id);
if (parent != null)
{
_dbContext.Parents.Remove(parent);
}
_dbContext.SaveChanges();
}
Include the Child when querying the parent as the following:
var parent = _dbContext.Parents
.include(p => p.Child)
.FirstOrDefault(p => p.Id == id);
Then call remove as you already doing.
Also, you may want to consider the OnDelete behavior to define what happens to Child when you delete the Parent. If you want the Child to be deleted when the parent is deleted use "Cascade" delete behavior. This can be done at database level in Child table definition similar to the following:
CONSTRAINT [FK_Childs_Parents_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [Parent] ON DELETE CASCADE
or at EF context level as the following:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Parent>()
.HasMany(e => e.Childs)
.WithOne(e => e.Parent)
.OnDelete(DeleteBehavior.ClientCascade);
}
Further related details from official Microsoft documentation here
If the model in the databaseContext snapshot has ".OnDelete(DeleteBehavior.Cascade)" you could just include the table and the child data will be removed.
var parent = _dbContext.Parents.Include(parent => parent.Children).FirstOrDefault(p => p.Id == id);
For this to work you should also make a list of Children in the parent model:
public record Parent
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Child> Children { get; set; }
}
This is the link to the Microsoft documentation

Cascade delete child rows when parent is deleted on same table in Entity Framework Core

The following is the entity on data context. I want to delete child zones when a parent zone is deleted.
I tried
cascade delete. This did not survive migration.
recursively listing all zones that should be deleted when zone z is deleted and use RemoveRange to delete all items in the list. It still throws an exception saying
Error:
DELETE statement conflicted with the SAME TABLE REFERENCE constraint
How do I delete items with such a simple relationship?
public class Zone
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
[ForeignKey("ParentId")]
public virtual Zone ParentZone { get; set; }
}
public class ZoneDbContext : DbContext
{
public ZoneDbContext(DbContextOptions<ZoneDbContext> options) : base(options)
{
}
public DbSet<Zone> Zones { get; set; }
}
This approach worked
public void DeleteMyEntity(MyEntity entity)
{
var target = MyEntities
.Include(x => x.Children)
.FirstOrDefault(x => x.Id == entity.Id);
RecursiveDelete(target);
SaveChanges();
}
private void RecursiveDelete(MyEntity parent)
{
if (parent.Children != null)
{
var children = MyEntities
.Include(x => x.Children)
.Where(x => x.ParentId == parent.Id);
foreach (var child in children)
{
RecursiveDelete(child);
}
}
MyEntities.Remove(parent);
}

Entity Framework One To Many Relationship mapping foreignkey causing null on navigation properties

I'm trying to build relationship while wanted to specify child and parent tables foreign key in addition to navigation property, model looks like this
public class Parent
{
public Parent()
{
this.Childern = new HashSet<Child>();
}
public Guid Id { get; set; }
public string Name { get; set; }
//public Guid Child_Id { get; set; }
public ICollection<Child> Childern { get; set; }
}
public class Child
{
public Child()
{
this.Parent = new Parent();
}
public Guid Id { get; set; }
public string Name { get; set; }
public Guid Parent_Id { get; set; }
public Parent Parent { get; set; }
}
With only above model when i build and run project, i get expected results on navigation properties as navigation properties are populated with expected data.
But i get additional un-wanted column in database as below
Id
Name
Parent_Id
Parent_Id1 (FK)
I further configured relationship in OnModelCreating like below
modelBuilder.Entity<Child>()
.HasRequired(p => p.Parent)
.WithMany(p => p.Childern)
.HasForeignKey(k => k.Parent_Id);
This time I got desired result on table structure now table looks like this
Id
Name
Parent_Id (FK)
But I'm getting null on navigation properties, please note that I'm trying Eagerly Loading
public static void TestParent()
{
using (var context = new ILDBContext())
{
var parents = context.Parents
.Include(p => p.Childern)
.ToList();
foreach (var parent in parents)
{
Console.WriteLine("Parent: {0} Childern: {1}", parent.Name, parent.Childern.Count());
foreach (var child in parent.Childern)
{
Console.WriteLine("Child: {0}", child.Name);
}
}
}
}
In addition I will be thankful if anyone can advise how should i configure relationship if i need FK in both models like, Parent_Id and Child_Id
Thanks.
You need to remove the Parent assignment in your Child constructor:
public Child()
{
this.Parent = new Parent();
}
has to be
public Child() {}
or you don't define it at all. For the ICollection<Child> in your Parent it doesn't really make a difference.

NHibernate does not work with tree object

i have this class that maps a tree of Tag objects :
public class Tag
{
public int Id { get; set; }
public int Description { get; set; }
private IList<Tag> childTag = new List<Tag>();
public virtual IEnumerable<Tag> ChildTag
{ get { return childTag.ToArray(); } }
public virtual void Add(Tag child)
{ childTag .Add(child); }
public virtual bool Remove(Tag child)
{ return childTag .Remove(child); }
}
with this map:
public TagMap()
{
Id(x => x.Id)
.Column("Kint_T01_IdTag")
.GeneratedBy.Assigned();
HasMany(x => x.ChildTag)
.KeyColumn("ParentId")
.Cascade.All()
.Access.CamelCaseField().ReadOnly();
}
when i run this test
Tag Tag= fixture.Build<Tag>().Do(x => x.Add(fixture.Create<Tag>())).Create();
Tag TagActual;
using (IUnitOfWork uow = new UnitOfWork())
{
uow.openConnection();
uow.BeginTran();
uow.TagRepository.Create(Tag);
uow.commit();
Tag.Rmove(Tag.ChildTag.First());
uow.TagRepository.Update(Tag);
uow.commit();
uow.closeConnection();
uow.openConnection();
TagActual = uow.TagRepository.GetById(Tag.Id);
TagActual.ShouldBeEquivalentTo(Tag);
uow.closeConnection();
}
it fails because TagActual.ChildTag contains a child even if i remove it from parent collection.
debugging the test i see that after
uow.TagRepository.Create(Tag);
uow.commit();
two record are isnerted in table and parent property of children is set correctly
Tag.Rmove(Tag.ChildTag.First()); work correctly and after that the collection is empty
but after
uow.TagRepository.Update(Tag);
uow.commit();
nothing change in table and 'parent' filed is still set.
In this way the test failed because TagActual has a collection of children while this should be empty
Why NHibernate correctly manage the add operation but not the remove
Is there a way to do it without adding Parent property to my object and manually manage it?
I find it :)
i done 2 mistakes:
.ReadOnly(); in map, it's useless
forgot beginTrans in test case
test must be written so
Tag Tag= fixture.Build<Tag>().Do(x => x.Add(fixture.Create<Tag>())).Create();
Tag TagActual;
using (IUnitOfWork uow = new UnitOfWork())
{
uow.openConnection();
uow.BeginTran();
uow.TagRepository.Create(Tag);
uow.commit();
uow.BeginTran();
Tag.Rmove(Tag.ChildTag.First());
uow.TagRepository.Update(Tag);
uow.commit();
uow.closeConnection();
uow.openConnection();
TagActual = uow.TagRepository.GetById(Tag.Id);
TagActual.ShouldBeEquivalentTo(Tag);
uow.closeConnection();
}
This is how NHibernate is designed, according to http://nhibernate.info/doc/nh/en/index.html#example-parentchild
Basically the solution is in adding an extra property "Parent" which is responsible for the relationship:
public class Tag
{
public int Id { get; set; }
public int Description { get; private set; }
private IList<Tag> childTag = new List<Tag>();
private Tag parentTag;
public virtual IEnumerable<Tag> ChildTag { get { return childTag.ToArray(); } }
public virtual void Add(Tag child) { childTag .Add(child); }
public virtual bool Remove(Tag child) { return childTag .Remove(child); }
}
with this map:
public TagMap()
{
Id(x => x.Id)
.Column("Kint_T01_IdTag")
.GeneratedBy.Assigned();
Reference(x => x.ParentTag).Access.CamelCaseField();
HasMany(x => x.ChildTag)
.Inverse()
.KeyColumn("ParentId")
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField().ReadOnly();
}
Now everything should work as expected...

Map foreign key in Fluent NHibernate without object property

My question is, is there a possible Fluent NHibernate mapping for Parent and Child objects that does not require the Child object to have a Parent object property? I haven't figured out how to map the reference back to the Parent. When I call Create with the mappings as-is, I get an exception because the Child object does not have the required foreign key (required in the data store) back to the Parent.
I have two POCO classes:
public class Parent
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Childs { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int ParentId { get; set; }
}
And some mappings:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
this.Table("Parents");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.HasMany(x => x.Childs).KeyColumn("ChildId").Cascade.AllDeleteOrphan();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
this.Table("Childs");
this.Id(x => x.Id);
this.Map(x => x.Name);
// Needs some sort of mapping back to the Parent for "Child.ParentId"
}
}
And Create method:
public Parent Create(Parent t)
{
using (this.session.BeginTransaction())
{
this.session.Save(t);
this.session.Transaction.Commit();
}
return t;
}
I want to be able to create a Parent object that has a list of Child objects, but not have the Child objects have references back to their Parent (other than the Parent ID). I want to do this to avoid the circular reference from Parent to a list of Childs back to the Parent object, since that is causing issues with JSON serialization.
You can map these entities without problem, try this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
this.Table("Parents");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.HasMany(x => x.Childs).KeyColumn("ChildId").Cascade.AllDeleteOrphan();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
this.Table("Childs");
this.Id(x => x.Id);
this.Map(x => x.Name);
this.Map(x => x.ParentId);
// if you have a reference of Parent object, you could map as a reference, for sample:
this.References(x => x.Parent).Column("ParentId");
}
}
When you get entities from ISession, do not serialize it to some format because these can be proxies of nhibernate instead entities objects. Try to create DTO (Data Transfer Object) classes and convert these entities to a DTO object, and serialize it. You will avoid circular references. For sample:
public class ParentDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
/* here you just have the primitive types or other DTOs,
do not reference any Entity type*/
}
And when you need to read the values to share the serialized value:
var dto = ISession.Query<Parent>()
.Select(x =>
new ParentDTO() {
Id = x.Id,
Name = x.Name,
ParentId = x.ParentId)
.ToList();
Get this result from the Data Access Layer and try to serialize, for sample:
var result = Serialize(dto);

Categories

Resources