One-Many via NHibernate Loquacious mapping by code or xml - c#

I want to implement a one-to-many relationship between a person and car, and have CRUD operations on both person and car. Brief CRUD and relationships:
Update
A person has many cars
CRUD operations on both person and car via person object.
Deleting a person will delete all s/his cars
Ability to perform CRUD operation on someone's cars, either via person object or car object.
Is it possible via ORM, in particular NHibernate?
Classes like below:
public class PersonSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<CarSet> Cars { get; set; }
}
public class CarSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual PersonSet Person { get; set; }
}
Mapping is shown below:
public class PersonSetMap : ClassMapping<PersonSet>
{
public PersonSetMap()
{
Id(x => x.Id, m=>m.Generator(Generators.Identity));
Property(x=>x.Name);
Set(x => x.Cars, c =>
{
//c.Key(k =>
// {
// k.Column("PersonId");
// });
c.Cascade(Cascade.All);
c.Lazy(CollectionLazy.NoLazy);
// c.Inverse(true);
}
, r =>
{
r.OneToMany();
}
);
}
}
public class CarSetMap : ClassMapping<CarSet>
{
public CarSetMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
ManyToOne(x => x.Person, m =>
{
m.Column("PersonId");
m.Cascade(Cascade.None);
m.NotNullable(true);
});
}
}
The problem I have is that if I update one car and try to save it on a person object, it doesn't change.
Update
I want to find out if it is possible, and where my mapping above is wrong. Any idea on either xml version or Loquacious would also be appreciated.
There should be a PersonId foreign key on table Car.

I don't know if this would solve your problem, but in a ManyToOne mapping the Unique and NotNullable methods should be applied at the column level.
ManyToOne(x => x.Person, m =>
{
m.Column(c =>
{
c.Name("PersonId");
c.NotNullable(true);
});
m.Cascade(Cascade.None);
});

Related

Fluent NHibernate: One to One Mapping - reverse from primary column to a foreign key

I have a "Group" class and a "GroupSummaryLevel" class, codes are given below. There is a one-to-one relation between these entities in DB. I need the "GroupSummaryLevel" as a property in Groups class. It is supposed to be a very simple join like
(SELECT g.Id FROM GroupSummaryLevel g WHERE g.AcctGroup = GroupID)
Unfortunately, I wasn't able to figure this out how to do with NHibernate. The many answers I saw here is no help to me. I would appreaciate any inputs from the more experienced NHibernate users out there. Thanks in advance.
public class Group : DomainEntity
{
public virtual string GroupId { get; set; }
public virtual string GroupName { get; set; }
public virtual GroupSummaryLevel GroupSummaryLevel { get; set; }
}
public class GroupSummaryLevel : DomainEntity
{
public virtual int Id { get; set; }
public virtual string AcctGroup { get; set; }
public virtual GroupSummaryLevel Parent { get; set; }
public virtual IList<GroupSummaryLevel> Children { get; set; }
public GroupSummaryLevel()
{
Children = new List<GroupSummaryLevel>();
}
}
The mapping I have done did not work so far. My mapping codes are as below:
public GroupMap()
{
Table("Groups");
LazyLoad();
Id(x => x.GroupId).GeneratedBy.Assigned().Column("GroupID").CustomType<TrimmedString>();
Map(x => x.GroupName).Column("GroupName").CustomType<TrimmedString>().Not.Nullable();
HasOne(x => x.GroupSummaryLevel).Cascade.None().ForeignKey("AcctGroup");
}
public GroupSummaryLevelMap()
{
Table("GroupSummaryLevel");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AcctGroup).Column("AcctGroup").CustomType<TrimmedString>().Not.Nullable();
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
}
Note: I also need to do a self-join for GroupSummaryLevel, and no success with that either. Any recommendations for that will also be appreciated :)
I would say, that your one-to-one is not driven by primary/foreign keys, but by property-ref. So the Group should map the summary by saying something like this:
...if you want to find related SummaryLevel, pass my <id> into column mapped as AcctGroup
public GroupMap()
{
...
HasOne(x => x.GroupSummaryLevel)
.Cascade.None()
//.ForeignKey("AcctGroup")
.PropertyRef(gsl => gsl.AcctGroup)
;
}
public GroupSummaryLevelMap()
{
...
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
References(x => x.Parent, "AcctGroup");
}
NOTEs for completeness, as discussed in comments:
In this scenario, when the "child" has reference to parent - it really calls for one-to-many/.HasMany() mapping.
The down side is, that child is represented as a colleciton of children: IList<GroupSummaryLevel>. It is not as straighforward to use, but we can create some virtual property, returning the .FirstOrDefault(). The benefit we get - is lazy loading (not in place with one-to-one).

Inserting a parent entity with existing child in Fluent NHibernate

This is a general question, I'm sure it's quite common, however I haven't found anything on it (or I don't know what to search for I guess).
I'm having the following entities in my project:
public class User
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Unit Unit { get; set; }
}
public class Unit
{
public virtual int Id { get; set;}
public virtual string Name { get; set;}
}
This is how I've done the Fluent NHibernate mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.Id).GeneratedBy.Identity().Column("UserId");
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Unit).Column("UnitId");
}
}
public class UnitMap : ClassMap<Unit>
{
public UnitMap()
{
Table("Unit");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("UnitId");
Map(x => x.Name).Column("Name").Not.Nullable();
HasMany(x => x.Users).KeyColumn("UnitId");
}
}
Now here is my question. How do I create a new User if I only have the user's unit Id not a full unit object, and the unit already exists in the database (created previously) ?
Something like this:
public class TestClass
{
// Adding a user to a unit example
public void SavingAUser(int unitId)
{
var user = new User
{
FirstName = "TestFirstName",
LastName = "TestLastName",
Unit = new Unit() // <-- I have only the Id of the unit I don't actually have a unit object here I don't want to query the DB to get the full object, I already have the Id
};
var userRepository = new UserRepository();
userRepository.Save(user);
}
}
How would I go about something like this. I hope I'm making sense, if not please let me know I'll throw in more clarifications. I'm also pretty certain that this is a very common scenario
You can return a proxy to the unit without fetching it.
var user = new User
{
FirstName = "TestFirstName",
LastName = "TestLastName",
Unit = Session.Load<Unit>(unitId)
}
You'll need to expose the session object.

Fluent nhibernate one-to-many

I have a one-to-many relationship and cannot get cascade to work, once I set cascade I just get "object references an unsaved transient instance ...".
My mapping looks like this
public class SharedDetailsMapping : ClassMap<SharedDetails>
{
public SharedDetailsMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Foos);
}
}
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.SharedDetails).Cascade.SaveUpdate();
}
}
The classes like this
public class Foo
{
public Foo()
{
SharedDetails = new SharedDetails();
SharedDetails.Foos.Add(this);
}
public Foo(SharedDetails sharedDetails)
{
SharedDetails = sharedDetails;
SharedDetails.Foos.Add(this);
}
public virtual Guid Id { get; set; }
public virtual SharedDetails SharedDetails { get; set; }
}
public class SharedDetails
{
public SharedDetails()
{
Foos = new List<Foo>();
}
public virtual Guid Id { get; set; }
public virtual IList<Foo> Foos { get; set; }
}
I then want to create Foos without having to save the SharedDetails first if its a new Foo, like so:
using (var transaction = _session.BeginTransaction())
{
var shared = new SharedDetails();
var fooOne = new Foo(shared);
_session.SaveOrUpdate(fooOne);
var fooTwo = new Foo(shared);
_session.SaveOrUpdate(fooTwo);
transaction.Commit();
}
Cannot figure out what I have done wrong, it works ofcurse if I save the SharedDetails first but that is why I have Cascade setup.
In your SharedDetailsMapping, modify your HasMany to add .Inverse():
public class SharedDetailsMapping : ClassMap<SharedDetails>
{
public SharedDetailsMapping()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Foos)
.Inverse();
}
}
This instructs NHibernate that Foo owns the relationship, which will help it save objects in the relationship in the right order (in this case, the SharedDetails has to get saved first so we have its ID when Foo is saved).
Further info on the purpose/when to use Inverse: NHibernate's inverse - what does it really mean?
The TL;DNR version:
If you have a bidirectional relationship in your classes (HasMany on one side, References on the other), the HasMany should have .Inverse().

NHibernate not loading child objects

I have following class and associated mappings (Fluent NHibernate):
public class Category
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
}
Mappings:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.AsBag().Fetch.Select();
}
}
I creating two Category object as follows:
var c = new Category
{
Name = "Ebooks",
Description = "Contains awz, mobi, pdf, epub and other
electronic books"
};
var cc = new Category
{
Name = "Kindle Books",
Description = "Contains Kindle ebook reader format books
(awz, mobi)",
ParentCategory = c
};
session.SaveOrUpdate(c);
session.SaveOrUpdate(cc);
When I try to access saved objects:
var c = session.Load<Category>(1);
var cc = c.ChildCategories;
c contains the Category object with Id of 1 but its ChildCategories property is null.
What I am doing wrong?
PS:- This is my first experiment with NHibernate and so with Fluent NHibernate.
EDIT:- The following stuff worked. I needed to close the session before opening it again for reading. Otherwise it read just from memory nad as #Holf has pointed out I needed to add Child Category to Category like:
c.ChilCategories.Add(cc);
I just did as follows:
var session = sf.OpenSession();
CreateCategory(session);//ADDED TWO CATEGORIES EBooks, Kindle Ebooks
session.Close();
session = sf.OpenSession();
FetchCategories(session);//READ CATEGORY EBooks AND ASSOCIATED CHILDREN
session.Close();
Although you've handled one side of the relationship, by assigning 'c' as the ParentCategory of 'cc', I can't see anywhere that you've done the inverse.
I think you'll also need to do
c.ChildCategories.Add(cc);
before doing the SaveOrUpdate.
Can you update your map to the following:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.Cascade.All();
}
}
Also you cannot be sure that the Id with value 1 refers to the Parent Category, use the LInq provider to load the correct object.

Difficulty Concerning EF Code First Fluent API, TPH, and Foreign Keys

I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.
My entity classes are as follows:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
}
class WidgetMap : EntityTypeConfiguration<Widget>
{
public WidgetMap()
{
ToTable("Widgets");
HasKey(w => w.Id);
Property(w => w.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(w => w.Name)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
}
}
public class WidgetTypeA : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
public string Color { get; set; }
public int DepthLevel { get; set; }
}
class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
public WidgetTypeAMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(1));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeAs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
Property(w => w.Color)
.IsOptional()
.IsUnicode(true)
.HasMaxLength(75);
Property(w => w.DepthLevel)
.IsOptional();
}
}
public class WidgetTypeB : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
public WidgetTypeBMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(2));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeBs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
}
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}
class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
ToTable("Users");
HasKey(u => u.Id);
Property(u => u.Username)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
Property(u => u.Age)
.IsRequired();
}
}
At any rate, I keep getting the error
Invalid column name 'UserId1'
when I try to perform the following operations:
using (var entities = new MyEntities())
{
User u = new User
{
Username = "Frank",
Age = 14
};
entities.Users.Add(u);
entities.SaveChanges();
WidgetTypeA wa1 = new WidgetTypeA
{
Name = "0SDF81",
UserId = u.Id,
DepthLevel = 6
};
entities.WidgetTypeAs.Add(wa1);
entities.SaveChanges();
}
Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?
You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.
I know its a long way late, but hopefully may help other readers.
Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.
It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.

Categories