NHibernate Delete Many-to-Many Parent and Child - nhibernate

I have 3 classes. Company, Address and Offices. Following are the entities definition.
Company:
public class Company: Identity
{
public Company()
{
Offices = Offices ?? new List<Office>();
}
public virtual string Name { get; set; }
public virtual IList<Office> Offices { get; set; }
public virtual void AddOffice(Office office)
{
office.Company = this;
Offices.Add(office);
}
}
Address:
public class Address: Identity
{
public Address()
{
CompanyOffices = CompanyOffices ?? new List<Office>();
}
public virtual string FullAddress { get; set; }
public virtual IList<Office> CompanyOffices { get; set; }
}
Office:
public class Office: Identity
{
public Office()
{
Company = Company ?? new Company();
Address = Address ?? new Address();
}
public virtual Company Company { get; set; }
public virtual Address Address { get; set; }
public virtual bool IsHeadOffice { get; set; }
}
Now for these classes i have following Mapping.
Company Mapping:
public class CompanyMapping: IdentityMapping<Company>
{
public CompanyMapping()
{
Map(x => x.Name);
HasMany(x => x.Offices).KeyColumn("CompanyId").Inverse().Cascade.AllDeleteOrphan();
}
}
Address Mapping:
public class AddressMapping: IdentityMapping<Address>
{
public AddressMapping()
{
Map(x => x.FullAddress);
HasMany(x => x.CompanyOffices).KeyColumn("AddressId").Inverse().Cascade.All();
}
}
Office Mapping:
public class OfficeMapping: IdentityMapping<Office>
{
public OfficeMapping()
{
Map(x => x.IsHeadOffice);
References(x => x.Company).Column("CompanyId").Cascade.None();
References(x => x.Address).Column("AddressId").Cascade.All();
}
}
Test Code:
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var companyOne = new Company { Name = "Company One" };
var companyTwo = new Company { Name = "Company Two" };
var addressOne = new Address { FullAddress = "Address One" };
var addressTwo = new Address { FullAddress = "Address Two" };
var officeOne = new Office { Company = companyOne, Address = addressOne, IsHeadOffice = true };
var officeTwo = new Office { Company = companyTwo, Address = addressTwo, IsHeadOffice = false };
var officeThr = new Office { Company = companyOne, Address = addressTwo, IsHeadOffice = true };
companyOne.AddOffice(officeOne);
companyTwo.AddOffice(officeTwo);
companyOne.AddOffice(officeThr);
session.SaveOrUpdate(companyOne);
session.SaveOrUpdate(companyTwo);
transaction.Commit();
}
}
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var companyOne = session.Get<Company>((long)1);
session.Delete(companyOne);
transaction.Commit();
}
}
Code Description:
Here a company is going to have multiple offices. Office is many to many relationship of company and address, but due to the fact that there could be many other columns (Office own data), I have changed the relationship from many to many to 2 one to many.
Question:
I want my application to delete any address that is left orphan when a company is deleted. But in this case when i delete my company, It will delete it's address as well. But it doesn't checks if the address is orphan. If there is another reference to address it will still be deleted. As per given above test code it should delete the "Company One" and "Address One" but not the "Address Two" as it is not orphan. But it deletes the "Address Two" as well.
Kindly can anyone let me know what's wrong with the above mapping?

I'm not sure if NHibernate does a global check for a DeleteOrphan, or only a Session check, a true global check would involve a DB query of course. But this actually isn't relevant here, the reason DeleteOrphan exists is for when you disassociate entities with parents (e.g. remove an item from the collection then call Update on the parent) but you are calling an operation on a top level entity which is cascading down directly.
What's really happening then is that you are calling Delete on a Company, as per the mapping on Offices, that is the All component of it, every child in the Offices collection thus has Delete called on it, because you have called Delete on the parent Company.
Since the mapping for Office also has a child Address, which is also mapped All, and Office just had Delete called on it, it will thus call Delete directly on it's Address child, since a direct Delete doesn't care about other associations or not (unless Session or the DB says so), the Address is simply deleted from the DB.
If you cannot change your entity structure here, then you will have to either stop the Delete reaching unorphaned Addresses (disassociate them manually first) or stop the Delete cascading to Address at all, then manage Address deletion totally manually.
I'm sure there are some better entity structures which can handle these relationships better if you have flexibility there though :P

Related

Seeding Data - Owned Type without Id

I have an Entity describing an organization, including its postal address. The Address is stored in a property (PostalAddress) and all properties of Organization is stored and flattened in the same database table. The configuration compiles and the migration is applied without any problems - if I don't seed data. I have tested standard CRUD in Razor Pages - no problem.
When adding a Seed in OnModelCreating I get an error when compiling.
The seed entity for entity type 'Organization.PostalAddress#PostalAddress' cannot be added because no value was provided for the required property 'OrganizationId'.
The message confuses me, as neither Organization, nor PostalAddress have an OrganizationId property. Neither does a shadow property exist in the db. Any ideas of what causes the problem?
public abstract class BaseEntity<TEntity>
{
[Key] public virtual TEntity Id { get; set; }
}
public class MyOrganization : BaseEntity<long>
{
public string Name { get; set; } // Name of the Organization
public PostalAddress PostalAddress { get; set; } // Postal address of the Organization
public string Email { get; set; } // Email of the Organization
}
public class PostalAddress
{
public string StreetAddress1 { get; set; } // Address line 1
public string ZipCode_City { get; set; } // Zip code
public string Country { get; set; } // Country
}
public void Configure(EntityTypeBuilder<Organization> builder)
{
builder
.ToTable("Organizations")
.HasKey(k => k.Id);
// Configure PostalAddress owned entity
builder
.OwnsOne(p => p.PostalAddress, postaladdress =>
{
postaladdress
.Property(p => p.StreetAddress1)
.HasColumnName("StreetAddress1")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
postaladdress
.Property(p => p.ZipCode_City)
.HasColumnName("ZipCode_City")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
postaladdress
.Property(p => p.Country)
.HasColumnName("Country")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
});
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder
.AddDBConfigurations();
// Seed data
builder
.Entity<Organization>(b =>
{
b.HasData(new Organization
{
Id = 1,
Name = "Test Organization",
Email = "nobody#nowhere.com"
});
b.OwnsOne(e => e.PostalAddress)
.HasData(new
{
StreetAddress1 = "1600 Pennsylvania Avenue NW", ZipCode_City = "Washington, D.C. 20500", Country = "USA"
});
});
}
The exception message is enigmatic, but helpful. When you add OrganizationId to the seeding code for PostalAddress it works.
modelBuilder
.Entity<Organization>(b =>
{
b.HasData(new Organization
{
Id = 1, // Here int is OK.
Name = "Test Organization",
Email = "nobody#nowhere.com"
});
b.OwnsOne(e => e.PostalAddress)
.HasData(new
{
OrganizationId = 1L, // OrganizationId, not Id, and the type must match.
StreetAddress1 = "1600 Pennsylvania Avenue NW",
ZipCode_City = "Washington, D.C. 20500",
Country = "USA"
});
});
Probably some undocumented convention is involved here. Which makes sense: the owned type can be added to more entity classes and EF needs to know which one it is. One would think that Id should be enough, after all, the type is clearly added to b, the Organization. I think some inner code leaks into the public API here and one day this glitch may be fixed.
Note that the type of the owner's Id must match exactly, while the seeding for the type itself accepts a value that has an implicit conversion.
it caused by convention if you need to make one to one relationship you need to add virtual property of MyOrganization and add organizationId in postal address object when seeding data this also help you to configure one to one relation https://www.learnentityframeworkcore.com/configuration/one-to-one-relationship-configuration

NHibernate table per type persist child from existing parent

I have 2 classes, Member.cs and Customer.cs and using table-per-type inheritance mapping described here.
This question poses the same problem, but with no answer.
Customer.cs
public class Customer
{
}
Member.cs
public class Member : Customer
{
public Member(Customer customer)
{
CreateFromCustomer(customer);
}
private void CreateFromCustomer(Customer customer)
{
// Here I assume I'll assign the Id so NHibernate wouldn't have to create a new Customer and know what Customer to be referred
Id = customer.Id;
}
}
CustomerMap.cs
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
}
}
MemberMap.cs
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
KeyColumn("Id");
}
}
I tried several test case :
Test1.cs
[Test]
public void CanAddCustomer()
{
var customerRepo = /* blablabla */;
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
var customer = new Customer()
customerRepo.RegisterCustomer(customer);
tx.Commit();
}
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
/* Get the persisted customer */
var customer = customerRepo.GetCustomerByWhatever();
var member = customerRepo.RegisterMember(new Member(customer));
tx.Commit();
}
}
I'm expecting to have :
1 customer and 1 member which is a child of that customer
Instead I have :
2 customers (1 that is what was correctly created and 1 with all null columns) and 1 member that Id referred to all null columns Customer.
Is it the expected behavior?
I understand if we were wanted to create a child object from a transient parent object, this is a correct behavior.
But what if we were to create a child object that refers to an existing parent object?
The link I provided doesn't cover any persistence example, neither does googling.
Short Answer
No, it is not possible to "upgrade" an already persisted object to its subclass. Nhibernate simply doesn't support this. That's why you see 2 customers and one member entry. This is actually the expected behavior because Nhibernate simply creates a copy with a new ID of the object instead of creating the reference to Member...
So basically you could do either
Copy the data of Customer into Member, delete customer and save Member
Use a different object structure without subclasses where Member is a different table with it's own ID and a reference to Customer
Use native sql to insert the row into Member...
Some example:
Your classes could look like this
public class Customer
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class Member : Customer
{
public virtual string MemberSpecificProperty { get; set; }
}
Basically, Member could have additional properties, but will have the same properties as Customer of cause, too.
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
and for the sub class, you have to map additional properties only!
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
Map(x => x.MemberSpecificProperty);
}
}
testing it
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Save(new Member()
{
Name = "Customer B",
MemberSpecificProperty = "something else"
});
session.Flush();
}
This will create 2 entries in the customer table and one row into Member table. So this is as expected, because we created one customer and one member...
Now the "upgrade" from Customer A to Member:
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Flush();
}
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
var customer = session.Query<Customer>().FirstOrDefault();
//var member = customer as Member;
var member = new Member()
{
Name = customer.Name,
MemberSpecificProperty = "something else"
};
session.Delete(customer);
session.Save(member);
session.Flush();
}

Losing the record ID

I have a record structure where I have a parent record with many children records. On the same page I will have a couple queries to get all the children.
A later query I will get a record set when I expand it it shows "Proxy". That is fine an all for getting data from the record since everything is generally there. Only problem I have is when I go to grab the record "ID" it is always "0" since it is proxy. This makes it pretty tough when building a dropdown list where I use the record ID as the "selected value". What makes this worse is it is random. So out of a list of 5 items 2 of them will have an ID of "0" because they are proxy.
I can use evict to force it to load at times. However when I am needing lazy load (For Grids) the evict is bad since it kills the lazy load and I can't display the grid contents on the fly.
I am using the following to start my session:
ISession session = FluentSessionManager.SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
I even use ".SetFetchMode("MyTable", Eager)" within my queries and it still shows "Proxy".
Proxy is fine, but I need the record ID. Anyone else run into this and have a simple fix?
I would greatly appreciate some help on this.
Thanks.
Per request, here is the query I am running that will result in Patients.Children having an ID of "0" because it is showing up as "Proxy":
public IList<Patients> GetAllPatients()
{
return FluentSessionManager.GetSession()
.CreateCriteria<Patients>()
.Add(Expression.Eq("IsDeleted", false))
.SetFetchMode("Children", Eager)
.List<Patients>();
}
I have found the silver bullet that fixes the proxy issue where you loose your record id!
I was using ClearCache to take care of the problem. That worked just fine for the first couple layers in the record structure. However when you have a scenario of Parient.Child.AnotherLevel.OneMoreLevel.DownOneMore that would not fix the 4th and 5th levels. This method I came up with does. I also did find it mostly presented itself when I would have one to many followed by many to one mapping. So here is the answer to everyone else out there that is running into the same problem.
Domain Structure:
public class Parent : DomainBase<int>
{
public virtual int ID { get { return base.ID2; } set { base.ID2 = value; } }
public virtual string Name { get; set; }
....
}
DomainBase:
public abstract class DomainBase<Y>, IDomainBase<Y>
{
public virtual Y ID //Everything has an identity Key.
{
get;
set;
}
protected internal virtual Y ID2 // Real identity Key
{
get
{
Y myID = this.ID;
if (typeof(Y).ToString() == "System.Int32")
{
if (int.Parse(this.ID.ToString()) == 0)
{
myID = ReadOnlyID;
}
}
return myID;
}
set
{
this.ID = value;
this.ReadOnlyID = value;
}
}
protected internal virtual Y ReadOnlyID { get; set; } // Real identity Key
}
IDomainBase:
public interface IDomainBase<Y>
{
Y ID { get; set; }
}
Domain Mapping:
public class ParentMap : ClassMap<Parent, int>
{
public ParentMap()
{
Schema("dbo");
Table("Parent");
Id(x => x.ID);
Map(x => x.Name);
....
}
}
ClassMap:
public class ClassMap<TEntityType, TIdType> : FluentNHibernate.Mapping.ClassMap<TEntityType> where TEntityType : DomainBase<TIdType>
{
public ClassMap()
{
Id(x => x.ID, "ID");
Map(x => x.ReadOnlyID, "ID").ReadOnly();
}
}

Can I cache entities with non mapped properties in NHibernates 2nd level cache?

Hi i have setup my SessionFactory to cache entities and queries:
private ISessionFactory CreateSessionFactory()
{
var cfg = new Configuration().Proxy(
properties => properties.ProxyFactoryFactory<DefaultProxyFactoryFactory>()).DataBaseIntegration(
properties =>
{
properties.Driver<SqlClientDriver>();
properties.ConnectionStringName = this.namedConnection;
properties.Dialect<MsSql2005Dialect>();
}).AddAssembly(this.resourceAssembly).Cache(
properties =>
{
properties.UseQueryCache = true;
properties.Provider<SysCacheProvider>();
properties.DefaultExpiration = 3600;
});
cfg.AddMapping(this.DomainMapping);
new SchemaUpdate(cfg).Execute(true, true);
return cfg.BuildSessionFactory();
}
This is my user mapping
public class UserMapping : EntityMapping<Guid, User>
{
public UserMapping()
{
this.Table("USERS");
this.Property(
x => x.CorpId,
mapper => mapper.Column(
c =>
{
c.Name("CorporateId");
c.UniqueKey("UKUserCorporateId");
c.NotNullable(true);
}));
this.Set(
x => x.Desks,
mapper =>
{
mapper.Table("DESKS2USERS");
mapper.Key(km => km.Column("UserId"));
mapper.Inverse(false);
mapper.Cascade(Cascade.All | Cascade.DeleteOrphans | Cascade.Remove);
},
rel => rel.ManyToMany(mapper => mapper.Column("DeskId")));
this.Cache(
mapper =>
{
mapper.Usage(CacheUsage.ReadWrite);
mapper.Include(CacheInclude.All);
});
}
}
What I want to do is get a user or query some users and add information to the domain object and cache the updated object.
public class User : Entity<Guid>, IUser
{
public virtual string CorpId { get; set; }
public virtual ISet<Desk> Desks { get; set; }
public virtual MailAddress EmailAddress { get; set; }
public virtual string Name
{
get
{
return string.Format(CultureInfo.CurrentCulture, "{0}, {1}", this.SurName, this.GivenName);
}
}
public virtual string GivenName { get; set; }
public virtual string SurName { get; set; }
}
something like this:
var users = this.session.Query<User>().Cacheable().ToList();
if (users.Any(user => user.EmailAddress == null))
{
UserEditor.UpdateThroughActiveDirectoryData(users);
}
return this.View(new UserViewModel { Users = users.OrderBy(entity => entity.Name) });
or this:
var user = this.session.Get<User>(id);
if (user.EmailAddress == null)
{
UserEditor.UpdateThroughActiveDirectoryData(user);
}
return this.View(user);
The UpdateThroughActiveDirectory methods work but are executed everytime i get data from the cache, the updated entities do not keep the additional data. Is there a way to also store this data in nhibernates 2nd level cache?
NHibernate doesn't cache entire entity in second level cache. It caches only the state / data from the mapped properties. You can read more about it here: http://ayende.com/blog/3112/nhibernate-and-the-second-level-cache-tips
There's an interesting discussion in comments of that post that explains this a little further:
Frans Bouma: Objects need to serializable, are they not? As we're talking about multiple appdomains. I wonder what's more
efficient: relying on the cache of the db server or transporting
objects back/forth using serialization layers.
Ayende Rahien: No, they don't need that. This is because NHibernate doesn't save the entity in the cache. Doing so would open
you to race conditions. NHibernate saves the entity data alone,
which is usually composed of primitive data (that is what the DB can
store, after all). In general, it is more efficient to hit a cache
server, because those are very easily scalable to high degrees, and
there is no I/O involved.

Mapping a many-to-many relationship as an IDictionary

I am trying to map a Person and Address class that have a many-to-many relationship. I want to map the Address collection as an IDictionary with the Address property Type as the key. The relationship is only mapped from the Person side.
public class Person
{
public IDictionary<int, Address> Addresses { get; set; }
}
public class Address
{
public int Type { get; set; }
}
The mapping I am using is:
HasManyToMany<Address>(x => x.Addresses).Table("PersonAddress")
.ParentKeyColumn("PersonId").ChildKeyColumn("AddressId")
.AsMap(x => x.Type);
The problem is that the SQL issued is:
SELECT addressesd0_.PersonId as PersonId1_,
addressesd0_.AddressId as AddressId1_,
addressesd0_.Type as Type1_,
address1_.AddressId as AddressId5_0_
-- etc.
FROM dbo.PersonAddress addressesd0_
left outer join dbo.Address address1_
on addressesd0_.AddressId = address1_.AddressId
WHERE addressesd0_.PersonId = 420893
It's attempting to select Type from the many-to-many join table, which doesn't exist. I've tried a number of variations of the mapping without success.
How can I map this?
It's not possible. Dictionaries need a key value in the relational table. The table structure you have is a simple many-to-many bag or set.
What I would do, is map it as a normal bag or set and provide dictionary like access in the entity:
public class Person
{
private IList<Address> addresses;
public IEnumerable<Address> Addresses { get { return addresses; } }
public Address GetAddressOfType(int addressType)
{
return addresses.FirstOrDefault(x => x.Type == addressType);
}
public void SetAddress(Address address)
{
var existing = GetAddressOfType(address.Type);
if (existing != null)
{
addresses.Remove(existing);
}
addresses.Add(address);
}
}
You need to use components:
HasMany<Address>(x => x.Addresses)
.AsMap<int>("FieldKey")
.Component(x => x.Map(c => c.Id));