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.
Related
I'm having some issues with understanding how to property set up one-to-one relationships for a Web API using Entity Framework Core.
Consider the following objects:
public class Car
{
[JsonIgnore]
public Int32 CarId { get; set; }
public virtual Horn Horn { get; set; }
public virtual ICollection<Wheel> Wheels { get; set; }
}
public class Horn
{
[JsonIgnore]
public Int32 HornId { get; set; }
public Sound Sound { get; set; }
}
public class Sound
{
// some other props
}
When I perform a query in my repository, one-to-many is by default excluded unless I use .Include(), however, for one-to-one properties, they are by default included when I serialize.
e.g. It gets really messy because I query for a car and it returns all it's sub-components in the JSON response.
I can set car.Horn = null, etc, but that seems difficult to maintain for complex objects. I would like it to function like .Include(), but by default be excluded (in case I do want to query the full object).
Edit: Note, this issue is recursive, the car pulls in horn which pulls in sound. On a real world example like a user table, the automatically pulled in data when serialized is huge unless specifically nullifying the child properties.
EDIT2:
Here is an example of a repository call that by default bring back all one-to-one properties:
var group = _context.Car.Include(c =>
c.Wheels).SingleOrDefault(u => u.CarId == id);
Note the Include works as expected for many-to-one, but this query, even when the Include is removed, will recursively return all child objects that are one-to-one.
It does appear some type of lazy loading is being introduced in EF CORE 2.1
This article should give you a hint.
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
Mainly:
Turn lazy loading off for serialization
Lazy loading and serialization don’t mix well, and if you aren’t
careful you can end up querying for your entire database just because
lazy loading is enabled. Most serializers work by accessing each
property on an instance of a type. Property access triggers lazy
loading, so more entities get serialized. On those entities properties
are accessed, and even more entities are loaded. It’s a good practice
to turn lazy loading off before you serialize an entity. The following
sections show how to do this.
EDIT:
Here is the way to disable lazy loading for all entities. But note you have to achieve this several way, so check the other options in the article...
public class YourContext : DbContext
{
public YourContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
Context mapping
modelBuilder.Entity<SessionFeedbackModel>(entity =>
{
entity.HasOne(s => s.Session).WithOne(p => p.Feedback)
.HasForeignKey<SessionFeedbackModel>(s => s.SessionId).OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<SessionQuestionModel>(entity =>
{
entity.HasOne(e => e.SessionResult).WithOne(e => e.SessionQuestion)
.HasForeignKey<SessionQuestionResultModel>(e => e.SessionQuestionId)
.OnDelete(DeleteBehavior.Restrict);
});
Models
public class SessionQuestionResultModel
{
public int Id { get; set; }
public int SessionQuestionId { get; set; }
public SessionQuestionModel SessionQuestion { get; set; }
}
public class SessionFeedbackModel
{
public int Id { get; set; }
public int SessionId { get; set; }
public SessionModel Session { get; set; }
}
EF Core 1.x or 2.x does not support 1 to 1 very well or at all, but it can be done this way, this would be radically different for EF 6.x.x
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
I have a situation with EF5 and a complex object. The basics is that I have a parent to child complex object, but the child refers back to the parent, more than once. I have tried various options but am not finding a solution that answers the question. The closest I have got is this answer (option 2c)
My model looks like below:
public class StaffMember
{
public virtual Guid StafId { get; set; }
// other props
// List of leave apps (Approved/Cancelled etc)
public virtual ICollection<StaffLeaveApp> LeaveApps { get; set; }
}
//Staff Leave Application
public class StaffLeaveApp
{
public virtual Guid LeaveId { get; set; }
public virtual Guid StaffId { get; set; }
// other props...
// Leave approved by? (2 approvals required)
public virtual StaffMember ApprovedBy1 { get; set; }
public virtual StaffMember ApprovedBy2 { get; set; }
}
my mappings look like this
public class StaffMap : EntityTypeConfiguration<StaffMember>
{
public StaffMap()
{
ToTable("tblStaffMembers");
HasKey(x => x.StaffId);
// other mappings...
HasMany(x => x.LeaveApps);
}
}
public class StaffLeaveAppMap: EntityTypeConfiguration<StaffLeaveApp>
{
public StaffLeaveAppMap()
{
ToTable("tblStaffMembersLeaveApps");
HasKey(x => x.LeaveId);
Property(x => x.StaffId).HasColumnName("StaffID");
//Child Relationships
HasOptional(x => x.ApprovedBy1).WithMany().Map(m => m.MapKey("LeaveApprovedBy1"));
HasOptional(x => x.ApprovedBy2).WithMany().Map(m => m.MapKey("LeaveApprovedBy2"));
}
}
Table (sorry, no images)
StaffID uniqueidentifier (FK - tblStaffMembers)
LeaveID uniqueidentifier (PK)
LeaveApprovedBy1 uniqueidentifier (FK - tblStaffMembers)
LeaveApprovedBy2 uniqueidentifier (FK - tblStaffMembers)
The business rule says: a staff member has "many" leave applications and a leave application belongs to a single staff member. Each application requires the approval of 2 staff members (managers) before it is "approved".
How would I map the above using EF so that a single staff member has a "many" leave applications (working already) and a leave application is mapped back to a staff member whom approved it for the first approval and then again for the seond approval. If I use the one mapping for "ApprovedBy1" only then EF is happy and all works as expected. The moment I add the second approval mapping EF struggles with the SQL queries it generates.
I am not sure how to tell EF to map back to the StaffMembers table to specify whom approved the application at level 1 and whom approved it at level 2. It almost ends up being a many to many relationship.
Any ideas?
You are looking for the inverse property, which is the property at the other end of an association. In EF, there are two way to mark a property as inverse.
Data annotations: InversePropertyAttribute.
Fluent mapping
As you already have fluent mapping I'll show you how you'd do it there:
HasOptional(x => x.ApprovedBy1).WithMany(x => x.LeaveApps)
.HasForeignKey(s => s.StaffId);
HasOptional(x => x.ApprovedBy2).WithMany()
.Map(m => m.MapKey("LeaveApprovedBy2"));
The HasOptional(...).WithMany(...) pair is a way to map inverse properties. Coming from the other side you can use e.g. HasMany(....).WithOptional(...).
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");
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.