I have a self referencing object Cycle:
public class Cycle
{
public Cycle()
{
ParentCycle = this;
ChildCycles = new List<Cycle>{this};
}
public virtual Guid Id { get; set; }
public virtual Cycle ParentCycle { get; set; }
public virtual IList<Cycle> ChildCycles { get; set; }
public virtual int Version { get; set; }
}
With the following mapping:
public class CycleMap : ClassMap<Cycle>
{
public CycleMap()
{
Table("Cycle");
Id(x => x.Id).Column("CycleID");
References(x => x.ParentCycle).Column("ParentCycleID").Not.Nullable();
HasMany(x => x.ChildCycles).KeyColumn("ParentCycleID").Cascade.AllDeleteOrphan().Inverse();
Version(x => x.Version);
}
}
I run the following test code:
var parentCycle = new Cycle();
session.Save(parentCycle);
session.Flush();
session.Delete(parentCycle);
session.Flush();
The creation of the cycle works, but when I try to delete the cycle, I have 2 problems:
Before the delete statement, NHibernate does an update statement to set the ParentCycle to NULL. However this property is not nullable, because if the cycle does not have a parent it references to himself.
When I make the property nullable another problem occurs. Nhibernate does the update which now succeeds but during this update it does not increment the version number. This is a problem for our auditing system. With every update the version should be incremented.
I'm wondering if anyone else had the same problems, the ideal solution would be to stop the update because it is unnecessary. But I cannot seem to achieve this.
Github
I made my test code available on GitHub
It should be easily possible, with setting called inverse="true"
public CycleMap()
{
...
HasMany(x => x.ChildCycles)
.KeyColumn("ParentCycleID")
// the setting, instructing NHibernate that other end will care...
.Inverse()
.Cascade.AllDeleteOrphan();
...
The point is, if NHibernate knows, that the other end (the inversed one) is taking care about the relationship, it does not have to issue
update (with null)
delete
The deep description of the inverse="true" could be found here (I would really suggest to read through, it is really well structured overview, still valid for NHibernate):
inverse = “true” example and explanation by mkyong
Related
I'm altering a business application to introduce a new entity SalesOrderReason. This will tie into another entity SalesOrder based on a SalesOrderReasonID field.
What I would like this code to do is to set the SalesOrderReasonID of the sales order (called from a button press - this is a desktop application).
In reality, what is happening is the first time it runs, it sets the SalesOrderReasonID - that's great. When I set the SalesOrderReasonID for the next SalesOrder it sets it correctly however it deletes a previous SalesOrder from the database which is obviously unwanted behavior.
I've had a similar issue previously where the relationship was configured incorrectly (I had put WithOne instead of WithMany) which gave the same symptoms, because of this I believe it's related to the configuration.
I've tried to configure it from the other perspective as well (SalesOrderReason config) without any success.
(Here is that attempt, I created an ICollection SalesOrdersWithReason on SalesOrderReason for this)
builder.HasMany(e => e.SalesOrdersWithReason)
.WithOne(s => s.SalesOrderReason)
.HasForeignKey(s => s.SalesOrderReasonID);
(Sorry if this is too much code I've tried to strip away what I thought was not relevant.)
This is the SalesOrder entity.
public class SalesOrder
{
public int ID { get; set; }
public int SalesOrderReasonID { get; set; }
public SalesOrderReason SalesOrderReason { get; set; }
}
This is the SalesOrderReason entity
public class SalesOrderReason
{
public int Id { get; set; }
public string Reason { get; set; }
public DateTime DateCreated { get; set; }
}
This is my configuration using fluent API
public class SalesOrderConfiguration : IEntityTypeConfiguration<SalesOrder>
{
public void Configure(EntityTypeBuilder<SalesOrder> builder)
{
builder.HasKey(e => e.ID);
builder.Property(e => e.ID)
.ValueGeneratedOnAdd();
builder.HasOne(s => s.SalesOrderReason)
.WithMany()
.HasForeignKey(s => s.SalesOrderReasonID);
}
}
This is the Save method in my Repository
public void Save() => _context.SaveChanges();
It all goes wrong here when saving a change to the SalesOrderReasonID
private static void ChangeSalesOrderReason(SalesOrder salesOrder, int salesOrderReasonID)
{
salesOrder.SalesOrderReasonID = salesOrderReasonID;
SalesOrderRepository.Save();
}
Edit to include UI code as requested
The SalesOrder in this use case is an existing object which comes from a Telerik gridview row, it gets selected then the user presses a button to update the SalesOrderReason on the SalesOrder to the one in a DropDownList.
The UI is in VB.Net (Odd I know but it's part of a legacy project we're rebuilding) the above C# code is in separate C# projects added as reference in the VB project.
Private Sub UpdateReasonButton_Click(sender As Object, e As EventArgs) Handles UpdateReasonButton.Click
For Each salesOrderRow As GridViewRowInfo In SalesOrderGridView.SelectedRows
SalesOrderController.ChangeSalesOrderReason(salesOrderRow.DataBoundItem, ReasonList.SelectedValue)
Next
End Sub
Following Panagiotis Kanavos's comment, I adjusted my DbContext to have a much shorter lifespan, I didn't have time to create a proper "Unit of Work" pattern but simply recreating the context whenever my code hit the repository seemed to do the trick.
I am working on a rather larger application that tries to follow a layered architecture pattern. On DBAL I use fluently configured NHibernate. Database objects sometimes have associations like these:
public class HeaderDbo
{
public HeaderDbo()
{
Details = new List<DetailDbo>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<DetailDbo> Details { get; set; }
}
public class DetailDbo
{
public virtual int Id { get; set; }
public virtual string DetailName { get; set; }
public virtual HeaderDbo Header { get; set; }
public virtual RelevantObjectDbo RelevantObject { get; set; }
}
public class RelevantObjectDbo
{
public virtual int Id { get; set; }
public virtual string RelevantText { get; set; }
}
Mapping is as follows:
public class HeaderDboMap : ClassMap<HeaderDbo>
{
public HeaderDboMap()
{
Table("Header");
Id(x => x.Id).Column("Id");
Map(x => x.Name);
HasMany(x => x.Details)
.Inverse()
.Cascade.All();
}
}
public class DetailDboMap : ClassMap<DetailDbo>
{
public DetailDboMap()
{
Id(x => x.Id).Column("Id");
Table("Detail");
Map(x => x.DetailName);
References(x => x.Header).Column("HeaderId");
References(x => x.RelevantObject).Column("RelevantObjectId")
.Cascade.SaveUpdate(); //??
}
}
public class RelevantObjectDboMap : ClassMap<RelevantObjectDbo>
{
public RelevantObjectDboMap()
{
Id(x => x.Id).Column("Id");
Table("RelevantObject");
Map(x => x.RelevantText);
}
}
Now, there are application domain entities that the DBOs are mapped to which do not necessarily reflect the database structure one-to-one. For example, Header might stay header, but Detail would, say, form itself from DetailDbo and parts of RelevantObjectDbo. Then the application does its thing over the entities - some transformation of Detail happens, which now needs to be persisted.
Suppose I only affected parts of the Detail entity which need to go into Detail table and don't affect RelevantObject table in any way. It might be a wrong way to think about the model but we also need to be practical about how persisting works. So, say, I would like to only have NHibernate update the Detail table without "touching" anything on the RelevantObject table. This is exactly the question, actually: how do I achieve that?
In reality, of course, the DB model is far bigger and more complicated and so is the application logic. There could be a part of BL that does not deal with the RelevantObject part of the data at all, so even though DBOs are loaded from the db fully, not all the data finds its way into the app model. But to persist the data back to the database - it seems that I need to hydrate the DB model fully and it's not always practical. So, how can we instruct NHibernate to "not touch" RelevantObject - in other words, not update dbo.Detail.RelevantObjectId?
I tried applying different Cascade options to DetailDbo.RelevantObject property, but if it stays at null, NHibernate always wants to set RelevantObjectId to NULL - I suppose, rightfully so.
I don't understand how I can write changes to the data that is relevant to my "part" of the BL without having to load and save half of my database through all the associations.
Thank you!
How are you performing these updates?
If you are not modifying anything on RelevantObject NHibernate will not send an update for that table. For example:
var header = session.Get<HeaderDbo>(1);
header.Details.First().DetailName = "Whatever";
session.Flush();
Should not cause an update to be issued to the RelevantObject table.
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();
I can't get my head around why this isn't working..
I have a relatively clean entity model consisting of POCOs created with DDD in mind (while probably not following most rules even loosely).
I am using Fluent NHibernate to do the mapping. I am also using SchemaExport to create the database schema, with minimum input from me on how to do it. NHibernate is free to choose the best way.
I have two entities with Many-to-many relationships with each other (non-interesting code removed); MediaItem and Tag; MediaItems can have many tags, Tags can be applied to many MediaItems, and I want collections on both sides so I can easily get at stuff.
(A bit of a formatting issue below, sorry)
MediaItem:
public class MediaItem
{
private IList<Tag> _tags;
public virtual long Id { get; set; }
public virtual string Title { get; set; }
public virtual IEnumerable<Tag> Tags { get { return _tags; } }
public MediaItem()
{
_tags = new List<Tag>();
}
public virtual void AddTag(Tag newTag)
{
_tags.Add(newTag);
newTag.AddMediaItem(this);
}
}
Tag:
public class Tag
{
private IList<MediaItem> _mediaItems;
public virtual long Id { get; set; }
public virtual string TagName { get; set; }
public virtual IEnumerable<MediaItem> MediaItems { get { return _mediaItems; } }
public Tag()
{
_mediaItems = new List<MediaItem>();
}
protected internal virtual void AddMediaItem(MediaItem newItem)
{
_mediaItems.Add(newItem);
}
}
I have tried to be smart about only exposing the collections as IEnumerable, and only allowing adding items through the methods. I also hear that only one side of the relationship should be responsible for this - thus the contrived AddMediaItem() on Tag.
The MediaItemMap looks like this:
public class MediaItemMap : ClassMap<MediaItem>
{
public MediaItemMap()
{
Table("MediaItem");
Id(mi => mi.Id);
Map(mi => mi.Title);
HasManyToMany<Tag>(mi => mi.Tags)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
The Tag mapping looks like this:
public class TagMap : ClassMap<Tag>
{
public TagMap()
{
Table("Tag");
Id(t => t.Id);
Map(t => t.TagName);
HasManyToMany<MediaItem>(mi => mi.MediaItems)
.Access.CamelCaseField(Prefix.Underscore)
.Inverse();
}
}
Now I have some test code that drops the database schema, recreates it (since I am shotgun debugging my brains out here), and then runs the following simple code:
Tag t = new Tag { TagName = "TestTag" };
MediaItem mi = new MediaItem { Title = "TestMediaItem" };
mi.AddTag(t);
var session = _sessionFactory.OpenSession();
session.Save(mi);
Yep, this is test code, it will never live past the problem in this post.
The MediaItem is saved, and so is the Tag. However, the association between them is not. NHibernate does create the association table "MediaItemsToTags", but it doesn't attempt to insert anything into it.
When creating the ISessionFactory, I specify ShowSQL() - so I can see all the DDL sent to the SQL server. I can see the insert statement for both the MediaItem and the Tag tables, but there is no insert for MediaItemsToTags.
I have experimented with many different versions of this, but I can't seem to crack it. Cascading is one possible problem, I've tried with Cascade.All() on both sides, Inverse() on both sides etc., but no dice.
Can anyone tell me what is the correct way to map this to get NHibernate to actually store the association whenever I store my MediaItem?
Thanks!
You need to define the many-to-many table and parent and child key columns:
public class MediaItemMap : ClassMap<MediaItem>
{
public MediaItemMap()
{
Table("MediaItem");
Id(mi => mi.Id);
Map(mi => mi.Title);
HasManyToMany<Tag>(mi => mi.Tags)
.Table("MediaItemsToTags").ParentKeyColumn("Id").ChildKeyColumn("Id")
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
The syntax is identical in TagMap because both key columns are named "Id".
Update:
It appears that changing my mapping from Cascade.All() to Cascade.AllDeleteOrphan() fixes most of my issues. I still have to explicitly set the Company property on the OperatingState, which seems unnecessary as it's being added to the Company entity, but at least I can work with that during an update. I still need to test that with a create.
If any one can explain that, that would be a big help.
Update 2: After playing with it some more, it appears I don't always have to specify the parent entity.
Original Post
I have 2 related entities
public class Company {
//... fields
public virtual IList<OperatingState> OperatingStates { get; set; }
}
public class OperatingState {
public virtual Company Company { get; set; }// Mapped on CompanyID
public virtual string State { get; set; }
}
And they are mapped like this:
public class CompanyMap : ClassMap<Company> {
public CompanyMap() {
//... fields
HasMany(x => x.OperatingStates)
.Cascade.All()
.Table("OperatingState");
}
}
public class OperatingStateMap : ClassMap<OperatingState> {
public OperatingStateStateMap() {
Id(x => x.ID);
References(x => x.Company);
Map(x => x.State);
}
}
So all is well until I try to update Company with new Operating States
Company company = _repo.GetSingle(123);
company.OperatingStates.Clear();
foreach(string state in form["OperatingStates"].Split(',')) {
company.OperatingStates.Add(new OperatingState(state));
}
_repo.Save(company); // calls ISession.SaveOrUpdate
It bombs out with:
Cannot insert the value NULL into
column 'CompanyID', table
'ConsumerCartel.dbo.CompanyOperatingState';
column does not allow nulls. INSERT
fails. The statement has been
terminated.
However, if I make 2 changes it kind of works
Company company = _repo.GetSingle(123);
// don't clear the list this time;
foreach(string state in form["OperatingStates"].Split(',')) {
OperatingState os = new OperatingState(state);
// explicitly setting the company
os.Company = company;
company.OperatingStates.Add(os);
}
_repo.Save(company); // calls ISession.SaveOrUpdate
Which will add the new states in addition to the old ones, which is not what I want. However, even when explicitly setting the company (which I shouldn't have to do when it's added to a mapped list?) it doesn't work if the list is cleared.
This code has worked on other entities, but not on this one, so I think this should work as written. What am I doing wrong?
Have you tried using Inverse()?
HasMany(x => x.OperatingStates)
.Inverse()
.Cascade.All()
.Table("OperatingState");