I try a simple Test Application with FluentNhibernate but it doesnt work as I expected.
Here are my classes:
public class Document : DataEntity
{
public virtual string Title { get; set; }
public virtual string FileName { get; set; }
public virtual DateTime LastModificationDate { get; set; }
public virtual User LastModificationBy { get; set; }
public virtual byte[] Content { get; set; }
public virtual User Owner { get; set; }
}
public class User : DataEntity
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Login { get; set; }
public virtual string PasswordHash { get; set; }
public virtual string Email { get; set; }
public virtual IList<Document> OwnedDocuments { get; set; }
public User()
{
this.OwnedDocuments = new List<Document>();
}
}
internal class UserMapping : ClassMap<User>
{
public UserMapping()
{
this.Id(x => x.Id);
this.Map(x => x.FirstName);
this.Map(x => x.LastName);
this.HasMany(x => x.OwnedDocuments).Inverse();
}
}
public DocumentMapping()
{
this.Id(x => x.Id);
this.Map(x => x.Title);
this.Map(x => x.FileName).Not.Nullable();
this.Map(x => x.LastModificationDate).Index("IDX_ModificationDate");
this.Map(x => x.Content);
this.References(x => x.LastModificationBy).Column("LastModificationBy");
this.References(x => x.Owner).Column("Owner");
this.Table("Document");
}
I try to test it with the following code
using (var transaction = Session.BeginTransaction())
{
var users = this.kernel.Get<IRepository<User>>();
var document = this.kernel.Get<IRepository<Document>>();
var user = new User { Login = "Blubb" };
users.Add(user);
var list = Builder<Document>.CreateListOfSize(50).All().With(x => x.Owner = user).Build().ToList();
var list2 = Builder<Document>.CreateListOfSize(50).All().Build().ToList();
list.ForEach(x => user.OwnedDocuments.Add(x));
document.Add(list);
document.Add(list2);
transaction.Commit();
var i = document.All().Count();
i.Should().Be(50);
var docs = ((IGuidKeyedRepository<User>)users).FindBy(user.Id).OwnedDocuments.Count();
docs.Should().Be(50);
}
The first problem is, why is the document count always 0 when I dont call document.Add(list);? I thought when I add some documents to the document collection of the user, they will be automaticly added to the documents?
And why ist the last test 100? Because I filter on the documents belongs to that user.
It looks like you need to set a cascade option on the child collection OwnedDocuments
this.HasMany(x => x.OwnedDocuments).Inverse().Cascade.AllDeleteOrphan();
The setting above will save all the children if you add any to the collection and if you remove them from the collection and save the object it will delete those child objects. You can find out more information about these settings in the nhibernate documentation:
http://www.nhforge.org/doc/nh/en/
Related
I have three tables in a many to many relationship with the nhibernate maps below. My objects are also below. A portfolio item can have many tags. The problem I am having is
1) update save another tag even when the name is the same as last time. So duplicate records get inserted into tag when the tag is the same. So for example if the tag for one portfolio object was abc the next portfolio item that adds the tag should reference this record rather than reinserting abc. I think this is because of the id column in the tag map. Nhibernate needs an id though.
2) Create does not add records in the join table. Records in the join table are only added on updates.
Domain Objects
public class Portfolio {
public Portfolio() {
PortfolioImage = new List<Portfolioimage>();
Tag = new List<Tag>();
}
public virtual int PortfolioId { get; set; }
public virtual string AliasTitle { get; set; }
public virtual string MetaDescription { get; set; }
public virtual string Title { get; set; }
public virtual string Client { get; set; }
public virtual string Summary { get; set; }
public virtual string Url { get; set; }
public virtual string MainImage { get; set; }
public virtual string TitleAlt { get; set; }
public virtual string Description { get; set; }
public virtual IList<Portfolioimage> PortfolioImage { get; set; }
public virtual IList<Tag> Tag { get; set; }
}
public class Portfoliotag {
public virtual int Id { get; set; }
public virtual Portfolio Portfolio { get; set; }
public virtual Tag Tag { get; set; }
}
public class Tag {
public Tag() {
Portfolio = new List<Portfolio>();
}
public virtual int TagId { get; set; }
public virtual string TagVal { get; set; }
public virtual IList<Portfolio> Portfolio { get; set; }
}
Maps
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.PortfolioId).GeneratedBy.Identity().Column("PortfolioId");
Map(x => x.AliasTitle).Column("AliasTitle").Not.Nullable();
Map(x => x.MetaDescription).Column("MetaDescription").Not.Nullable();
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Client).Column("Client").Not.Nullable();
Map(x => x.Summary).Column("Summary").Not.Nullable();
Map(x => x.Url).Column("Url");
Map(x => x.MainImage).Column("MainImage");
Map(x => x.TitleAlt).Column("TitleAlt");
Map(x => x.Description).Column("Description").Not.Nullable();
HasMany(x => x.PortfolioImage).KeyColumn("PortfolioId").Inverse();
HasManyToMany(x => x.Tag).Table("PortfolioTag").ParentKeyColumn("PortfolioId").ChildKeyColumn("TagId").LazyLoad().Cascade.All().Fetch.Join();
}
}
public class PortfoliotagMap : ClassMap<Portfoliotag> {
public PortfoliotagMap() {
Table("PortfolioTag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Portfolio).Not.Nullable().Cascade.SaveUpdate().Column("PortfolioId");
References(x => x.Tag).Not.Nullable().Cascade.SaveUpdate().Column("TagId");
}
}
public class TagMap : ClassMap<Tag> {
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.TagId).GeneratedBy.Identity().Column("TagId");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
//HasMany(x => x.PortfolioTag).KeyColumn("TagId");
// HasMany(x => x.PortfolioTag).Cascade.AllDeleteOrphan().Inverse().Fetch.Join().KeyColumn("TagId");
HasManyToMany(x => x.Portfolio).Table("PortfolioTag").ParentKeyColumn("PortfolioId").ChildKeyColumn("TagId").LazyLoad().Inverse().Cascade.AllDeleteOrphan();
}
}
given the classes
public class Portfolio
{
public Portfolio()
{
Tag = new List<Tag>();
}
public virtual int PortfolioId { get; protected set; }
public virtual IList<Tag> Tag { get; protected set; }
}
public class Tag
{
public virtual int TagId { get; set; }
public virtual string Name { get; set; }
}
the this mapping should suffice
public class PortfolioMap : ClassMap<Portfolio>
{
public PortfolioMap()
{
Id(x => x.PortfolioId).GeneratedBy.Identity().Column("PortfolioId");
HasManyToMany(x => x.Tag)
.Table("PortfolioTag")
.ParentKeyColumn("PortfolioId")
.ChildKeyColumn("TagId")
.Cascade.All()
.Fetch.Join();
}
}
assigning existing tags has to be handled in code which can cache or query involved tags more efficiently than automatic querying by any framework.
Update:
example usage which works for me
public void Save(int portfolioId, IEnumerable<string> tagnames)
{
using (var tx = session.BeginTransaction())
{
var tags = Session.QueryOver<Tag>().WhereProperty(x => x.Name).In(tagnames).List();
var portfolio = session.Get<Portfolio>(portfolioId);
portfolio.Tags.Clear();
portfolio.Tags.AddRange(tags);
tx.Commit();
}
}
and should I save the DTOs directly to the database?
public class Product
{
public virtual int ProductId { get; set; }
public virtual string ProductCode { get; set; }
public virtual string ProductName { get; set; }
public virtual Category Category { get; set; }
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ProductId);
Map(x => x.ProductCode);
Map(x => x.ProductName);
References(x => x.Category).Column("CategoryId");
}
}
public class ProductDto
{
public virtual int ProductId { get; set; }
public virtual string ProductCode { get; set; }
public virtual string ProductName { get; set; }
public virtual int CategoryId { get; set; }
}
public class ProductDtoMap : ClassMap<ProductDto>
{
public ProductDtoMap()
{
Table("Product");
Id(x => x.ProductId);
Map(x => x.ProductCode);
Map(x => x.ProductName);
Map(x => x.CategoryId);
}
}
Here's how I create, open and save record:
public ActionResult Input()
{
return View(new ProductDto());
}
public ActionResult Edit(int id)
{
using(var s = SessionFactoryBuilder.GetSessionFactory().OpenSession())
{
return View("Input", s.Get<ProductDto>(id));
}
}
// Save
[HttpPost]
public ActionResult Input(ProductDto p)
{
if (ModelState.IsValid)
{
using (var s = SessionFactoryBuilder.GetSessionFactory().OpenSession())
{
s.Merge(p);
s.Flush();
}
return RedirectToAction("Index");
}
else
{
return View(p);
}
}
Suffice to say, I wanted convenience, I want to persist the DTOs directly to database, and retrieve them back directly too.
Now what need do I have for Product class if I just use ProductDto exclusively for CRUD? I'll just use Product class for reporting only :-)
Is populating DTOs directly from ORM and saving them back directly to ORM a sound practice?
Yes if you can do it, it's the best way. Don't feel guilty about it :)
My application has the following entities:
public class User
{
public virtual int UserID { get; set; }
public virtual string UserName { get; set; }
public virtual IList<UserLog> Log { get; private set; }
public User()
{
Log = new List<UserLog>();
}
}
public class UserLog
{
public virtual int UserLogID { get; set; }
public virtual User User { get; set; }
public virtual string UserName { get; set; }
public virtual DateTime DateCreated { get; set; }
}
With the following fluent mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
HasMany(x => x.Log)
.KeyColumn("UserID")
.OrderBy("DateCreated")
.Inverse()
.Cascade.All();
}
}
public class UserLogMap : ClassMap<UserLog>
{
public UserLogMap()
{
Table("UsersLog");
Id(x => x.UserLogID);
References(x => x.User, "UserID");
Map(x => x.UserName);
Map(x => x.DateCreated);
}
}
The UsersLog table simply logs any changes which are made to the User. I'm trying to hook this up automatically using the NHibernate event listeners. I have setup my configuration which successfully calls the following 2 methods:
public void OnPostInsert(PostInsertEvent #event)
{
if (#event.Entity is User)
InsertLog(#event.Entity);
}
public void OnPostUpdate(PostUpdateEvent #event)
{
if (#event.Entity is User)
InsertLog(#event.Entity);
}
Edit (here is the InsertLog method):
private void InsertLog(object entity)
{
var context = ServiceLocator.Current.GetInstance<IDataContext>();
var user = (User)entity;
context.Repository<UserLog>().Insert(new UserLog
{
User = user,
UserName = user.UserName,
DateCreated = DateTime.UtcNow
}); // Insert calls ISession.SaveOrUpdate(entity)
}
When i update a record a log is successfully inserted but when i insert a record i get an error telling me the UserID cannot be null in the UsersLog table. I've played around with a few different variations but nothing seems to work.
I'd really appreciate it if someone could show me how this can be done. Thanks
Solution posted in question comments. I simply had to use:
#event.Session.GetSession(NHibernate.EntityMode.Poco).Save(...);
to save the user instead of accessing the repository.
I'm feeling dumb.
public class Uber
{
public Foo Foo { get; set; }
public Bar Bar { get; set; }
}
public class Foo
{
public string Name { get; set; }
}
...
var ubercharged = session.CreateCriteria(typeof(Uber))
.Add(Expression.Eq("Foo.Name", "somename"))
.UniqueResult<Uber>();
return ubercharged;
This throws a "could not resolve property" error.
What am I doing wrong? I want to query for an Uber object that has a property Foo which has a Name of "somename".
updated with real life example, repository call, using fluent nhibernate:
public UserPersonalization GetUserPersonalization(string username)
{
ISession session = _sessionSource.GetSession();
var personuser = session.CreateCriteria(typeof(UserPersonalization))
.Add(Expression.Eq("User.Username", username))
.UniqueResult<UserPersonalization>();
return personuser;
}
The classes/mappings:
public class User
{
public virtual Guid UserId { get; set; }
public virtual string Username { get; set; }
public virtual string Email { get; set; }
public virtual string PasswordHash { get; set; }
public virtual string PasswordSalt { get; set; }
public virtual bool IsLockedOut { get; set; }
public virtual bool IsApproved { get; set; }
}
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class UserPersonalization
{
public virtual int UserPersonalizationId { get; set; }
public virtual Person Person { get; set; }
public virtual User User { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserId).GeneratedBy.Guid().ColumnName("UserId");
Map(x => x.Username);
Map(x => x.PasswordHash);
Map(x => x.PasswordSalt);
Map(x => x.Email);
Map(x => x.IsApproved);
Map(x => x.IsLockedOut);
}
}
public class UserPersonalizationMap : ClassMap<UserPersonalization>
{
public UserPersonalizationMap()
{
WithTable("UserPersonalization");
Id(x => x.UserPersonalizationId).ColumnName("UserPersonalizationId");
References(x => x.Person).ColumnName("PersonId");
References(x => x.User).ColumnName("UserId");
}
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.PersonId).ColumnName("PersonId");
Map(x => x.Name);
References(x => x.Company).ColumnName("CompanyId");
}
}
Try this:
var ubercharged = session.CreateCriteria(typeof(Uber))
.CreateCriteria("Foo")
.Add(Restrictions.Eq("Name", "somename"))
.UniqueResult<Uber>();
Can you sort using "ubercharged.AddOrder(Order.asc("Foo.Name")) syntax? This syntax should work in NHib 2.01. If not, your maps are not working correctly.
Stuart's answer should work fine for you though.
How would you map the following in Fluent NHibernate?
See "18.3. Customer/Order/Product"
http://www.hibernate.org/hib_docs/nhibernate/html/example-mappings.html
The following solution uses the same approach as the solution in the example, and the generated XML is as good as the same. I have omitted specifying column names and such things for brevity.
Domain:
public class Customer
{
private ISet<Order> orders = new HashedSet<Order>();
public long Id { get; set; }
public string Name { get; set; }
public ISet<Order> Orders
{
get { return orders; }
private set { orders = value; }
}
}
public class Order
{
public long Id { get; set; }
public DateTime Date { get; set; }
public Customer Customer { get; set; }
public IList<LineItem> LineItems { get; private set; }
}
public class LineItem
{
public int Quantity { get; set; }
public Product Product { get; set; }
}
public class Product
{
public long Id { get; set; }
public string SerialNumber { get; set; }
}
Mapping:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.Name);
HasMany<Order>(x => x.Orders)
.IsInverse()
.AsSet();
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.Date);
References<Customer>(x => x.Customer);
HasMany<LineItem>(x => x.LineItems)
.Component(c =>
{
c.Map(x => x.Quantity);
c.References<Product>(x => x.Product);
}).AsList();
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id)
.GeneratedBy.Native();
Map(x => x.SerialNumber);
}
}
To see the generated XML mapping, you can use this code:
Configuration config = new Configuration().Configure();
PersistenceModel model = new PersistenceModel();
model.addMappingsFromAssembly(typeof(CustomerMap).Assembly);
model.Configure(config);
model.WriteMappingsTo("your folder name here");
I hope it helps.
/Erik