I'm having trouble persisting primitive type collection using (Fluent)NHibernate.
Here's the entity and mapping:
public class SomeOne
{
public virtual long ID { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Iesi.Collections.Generic.ISet<string> Foo { get; protected set; }
public SomeOne()
{
Foo = new HashedSet<string>();
}
}
public SomeOneMap()
{
Id(x => x.ID).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
HasMany(x => x.Foo).Element("Code").AsSet().Not.Inverse();
Table("SomeTypeOne");
}
However, when I try to save SomeOne instance, associated Foo strings get's ignored.
var session = factory.OpenSession();
var one = new SomeOne();
one.Foo.Add("Dato");
one.Foo.Add("Mari");
session.Save(one);
Any idea what could be wrong?
Thanks
UPDATE
Here's db schema. it's generated by NH.
There are two ways of ensuring your collected is persisted.
Call session.Flush(); after session.Save(one);. This will cause NHibernate to persist your collection. More information on flush can be found here.
Wrap the whole thing in a transaction, i.e.
using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var one = new SomeOne();
one.Foo.Add("Dato");
one.Foo.Add("Mari");
session.Save(one);
transaction.Commit();
}
There are several reasons why option two is better than option one. The main advantage is that all objects are saved back to the DB within the same transaction, so if one insert fails then the transaction is rolled back and your DB is not left in an inconsistent state. The acceped answer to this question has an extensive explanation of why you should use transactions with NHibernate.
Related
I dont normally deal with data like this but I thought id give it a try. As it turned out I failed :/ and am not sure how to proceed.
I have a database object track:
public virtual string Type { get; set; }
public virtual IList<DateTypeTrack> TrackDates { get; set; }
With a mapping file:
Table("Tracks");
Map(x => x.Type).Not.Nullable();
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All();
The DateTypeTrack Object looks like this:
public virtual DateType DateType { get; set; }
public virtual Track Track { get; set; }
public virtual int Days { get; set; }
With a mapping file like this:
Table("DateTypeTracks");
References(x => x.DateType, "DateTypeID").Not.Nullable();
References(x => x.Track, "TrackID").Not.Nullable();
Map(x => x.Days).Not.Nullable();
If its necessary, Ill post the DateType code aswell, but I dont think its needed.
And am trying to write a delete method in my service layer that is pretty simple:
public void PushDelete(int id)
{
Track track = _tracks.Get(id);
try
{
_tracks.BeginTransaction();
_tracks.Delete(track);
_tracks.CommitTransaction();
}
catch (Exception)
{
_tracks.RollbackTransaction();
throw;
}
}
I keep getting an error:
could not delete collection: [TSE.Domain.DatabaseObjects.Track.TrackDates#12][SQL: UPDATE DateTypeTracks SET TrackID = null WHERE TrackID = #p0]
I dont know why its trying to do the update at the end, but I suppose that is what is causing the issue. What sort of recourse do I have?
Thanks.
since the DateTypeTrack already cares for the association between the two entities you should mark the HasMany as Inverse to tell NH that the hasmany does not maintain it (the Update)
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All().Inverse();
I'm using Nhibernate 3.2 and fluent nhibernate, I have two tables Customer Group and Customer, and I use for lock management version control with TimeStamp Column.
I have the following classes and maps for these classes:
public class Customer
{
public Customer()
{
}
public virtual int CustomerID { get; set; }
public virtual CustomerGroup customerGroup { get; set; }
public virtual int CustomerGroupID { get; set; }
public virtual string CustomerRef { get; set; }
public virtual string NameE { get; set; }
public virtual string NameA { get; set; }
public virtual byte[] TimeStamp { get; set; }
}
and his map
public class CustomerMap : ClassMap<Customer> {
public CustomerMap() {
Table("Customer");
Id(x => x.CustomerID).GeneratedBy.Identity().Column("CustomerID");
Version(x =>x.TimeStamp).CustomType("BinaryBlob").Generated.Always().Column("TimeStamp");
DynamicUpdate();
OptimisticLock.Version();
References(x =>x.customerGroup).Column("CustomerGroupID").ForeignKey("CustomerGroupID");
Map(x => x.CustomerRef).Column("CustomerRef").Length(30).Unique();
Map(x => x.NameE).Column("NameE").Not.Nullable().Length(100).Unique();
Map(x => x.NameA).Column("NameA").Length(100);
and for Customer Group:
public class CustomerGroup {
public CustomerGroup() {
Customers = new List<Customer>(3);
}
public virtual int CustomerGroupID { get; set; }
public virtual IList<Customer> Customers { get; set; }
public virtual byte[] TimeStamp { get; set; }
}
and his map:
public CustomerGroupMap() {
Table("CustomerGroup");
Version(x => x.TimeStamp).CustomType("BinaryBlob").Generated.Always().Column("TimeStamp");
DynamicUpdate();
OptimisticLock.Version();
Id(x => x.CustomerGroupID).GeneratedBy.Identity().Column("CustomerGroupID");
HasMany(x => x.Customers).KeyColumn("CustomerGroupID");
}
When I create update in list of customers belong to specific Customer Group like this:
ISession Session = OpenSession();
Session.BeginTransaction();
var customerGroupInfo = Session.Query<CustomerGroup>().Fetch(x => x.Customers).Single<CustomerGroup>(x => x.CustomerGroupID == 98);
foreach (var item in customerGroupInfo.Customers)
{
item.NameE = "abc";
Session.Update(item);
}
Session.Transaction.Commit();
apply these sql statements:
UPDATE Customer SET NameE = 'abc'
WHERE CustomerID = 200 AND TimeStamp = 0x00000000000092EF
SELECT customer_.TimeStamp as TimeStamp1_ FROM Customer customer_
WHERE customer_.CustomerID = 200
UPDATE Customer SET NameE = 'abc'
WHERE CustomerID = 201 AND TimeStamp = 0x00000000000092F0
SELECT customer_.TimeStamp as TimeStamp1_ FROM Customer customer_
WHERE customer_.CustomerID = 201
.
.
.
and every update and every select operate in single round trip.
I set property adonet.batch_size property in configuration like this:
<property name="adonet.batch_size">20</property>
I read in this post this behavior founded by default in Nhibernate 3.2.
Any Tips to make batch work correctly?
You might look at changing your Session.FlushMode to something other than Automatic. That way, you could do something like this:
Session.FlushMode = NHibernate.FlushMode.Never
foreach (var item in customerGroupInfo.Customers)
{
item.NameE = "abc";
Session.Update(item);
}
Session.Flush();
Session.Transaction.Commit();
// Perhaps changing the flushmode after commit?
Session.FlushMode = NHibernate.FlushMode.Auto;
Edit :
Nevermind, see this excerpt from the docs: http://nhibernate.info/doc/nh/en/index.html#batch
It appears that batching doesn't get along with optimistic locking.
NHibernate supports batching SQL update commands (INSERT, UPDATE, DELETE) with the following limitations:
.NET Framework 2.0 or above is required,
**the Nhibernate's drive used for your RDBMS may not supports batching,**
since the implementation uses reflection to access members and types in System.Data assembly which are not normally visible, it may not function in environments where necessary permissions are not granted
**optimistic concurrency checking may be impaired since ADO.NET 2.0 does not return the number of rows affected by each statement in the batch, only the total number of rows affected by the batch.**
i have written a code like this
public class person
{
public person()
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
}
}
public class Child : Person
{
public person()
{
public virtual string Name{ get; set; }
public virtual string Lastname{ get; set; }
}
}
public class Book
{
public virtual int Id { get; set; }
public virtual string Name {get;set;}
}
and my Mapper classes is like these
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Table("tblPersons");
Id(x => x.Id).GeneratedBy.Native();
Map(p => p.Code);
JoinedSubClass<Child>("Id", MapChild);
}
public static void MapChild(JoinedSubClassPart<Child> child)
{
child.Table("tblChilds");
child.Map(p => p.Name);
child.Map(p => p.Lastname);
child.HasMany(x => x.Relatives);
}
}
public class RelativeMapping : ClassMap<Relative>
{
public RelativeMapping()
{
Table("tblRelatives");
Id(x => x.Id);
Map(p => p.Name);
References(x => x.Child).Column("ChildId");
}
}
this is Configuration
Assembly assm = Assembly.Load("BLL");
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c
.FromAppSetting("ConnStr"))
.Cache(c => c
.UseQueryCache()
.ProviderClass<HashtableCacheProvider>())
.ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssembly(assm))
.BuildSessionFactory();
and this is the code of delete
public void Delete<T>(T obj)
{
ISessionFactory fact = FluentConfiguration.CreateSessionFactory();
ISession session = fact.OpenSession();
session.Delete(obj);
session.Flush();
}
my problem : when i wanna delete a Child it messages
" ILLEGAL ATTEMPT TO ASSOCIATE WITH TWO OPEN SESSION "
please help me
The error tells you the problem, and it is not with your entities or your mapping.
You have two or more open sessions and you're attempting to associate some entity with more than one of them.
update
In response to the updated code, I see that you have a method that accepts an entity as a parameter, creates a new session factory, creates a new session, and then tries to delete the entity.
There are some problems here:
You should only create the session factory once. Ever. This is an expensive operation.
You are passing the entity to the Delete() method. Where is this entity coming from? You've clearly already loaded it elsewhere in your application, using a different ISession. This is the crux of the problem. Unless you Evict() the entity from the first ISession (not recommended), trying to manipulate it with a different ISession will throw.
You're calling Flush() which should almost never be used.
You're using an implicit transaction.
You should really be deleting the entity with the same ISession with which it was loaded, and you should be performing work within a transaction, like this:
using(var transaction = session.BeginTransaction())
{
session.Delete(obj);
transaction.Commit();
}
I'm trying to adopt Fluent NHibernate with my project, currently I can get data from database, when I'm at application server, data is include its PK but when I return this data (as List) to client all of its PK is loose.
How can I fixed this problem?
Update
My POCO class is below: PKs are CountryCd and CityCd
public class coCity
{
public virtual string CountryCd { get; private set; }
public virtual string CityCd { get; private set; }
public virtual string CityNameTH { get; set; }
public virtual string CityNameEN { get; set; }
public virtual int DeliveryLeadTime { get; set; }
public virtual string CreateBy { get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual string UpdateBy { get; set; }
public virtual DateTime UpdateDate { get; set; }
public override bool Equals(object obj)
{
return this.GetHashCode().Equals(obj.GetHashCode());
}
public override int GetHashCode()
{
return (this.CountryCd + this.CityCd).GetHashCode();
}
}
Mapping class:
public class coCityMap : ClassMap<coCity>
{
public coCityMap()
{
Table("coCity"); // this is optional
CompositeId()
.KeyProperty(x => x.CountryCd)
.KeyProperty(x => x.CityCd);
Map(x => x.CityNameTH);
Map(x => x.CityNameEN);
Map(x => x.DeliveryLeadTime);
Map(x => x.CreateBy);
Map(x => x.CreateDate);
Map(x => x.UpdateBy);
Map(x => x.UpdateDate);
}
}
Source code to get data at application server
public List<coCity> GetTest()
{
List<coCity> result = new List<coCity>();
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
result = (List<coCity>)session.CreateCriteria(typeof(coCity)).List<coCity>();
}
return result;
}
When its still at application server data is retrieve correctly as image below
alt text http://img138.imageshack.us/img138/1071/serverside.png
However when this data transit back to client side all of its PKs is loose like below.
alt text http://img203.imageshack.us/img203/1664/clientside.png
First of all, this isn't a problem with Fluent NHibernate so:
Serializable must be used on your POCO's when you serialize them.
(from your comment) NHibernate keeps a reference of the object retrieved from the database to a cache (1-st level cache). While you serialize this 'managed' object the output of the serialization is an unmanaged object. Nhibernate does not detect that a an object exists in the db just because you set an value in a newly constructed object. You must get the object from the database and update its properties and call Update() or you work with pure sql with the object that returned from the client (yikes!).
Note that is irrelevant with this question: your Equals() implementation is really bad as it doesn't take into account types and depends only on GetHashCode value. If all your classes have this implementation you could run into trouble.
I think the problem is with that private setter on the PK's properties. Try changing that to public.
Either way, mark your entity with Serializable
A few comments:
As a general recomendation when using nhibernate is to avoid composite Ids. Create on your model a surrogate Id that is an identity column and enforce uniqueness of CityCd and CountryCd somewhere else
When passing data around client/server tiers, consider using DTOs to avoid some commong LazyInitializationExceptions problems.
I am trying to figure out what I thought was just a simple one to many mapping using fluent Nhibernate. I hoping someone can point me to the right directory to achieve this one to many relations
I have an articles table and a categories table
Many Articles can only belong to one Category
Now my Categores table has 4 Categories and Articles has one article associated with cateory1
here is my setup.
using FluentNHibernate.Mapping;
using System.Collections;
using System.Collections.Generic;
namespace FluentMapping
{
public class Article
{
public virtual int Id { get; private set; }
public virtual string Title { get; set; }
public virtual Category Category{get;set;}
}
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class ArticleMap:ClassMap<Article>
{
public ArticleMap()
{
Table("Articles");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Title);
References(x => x.Category).Column("CategoryId").LazyLoad();
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
}
}
}
}
if I run this test
[Fact]
public void Can_Get_Categories()
{
using (var session = SessionManager.Instance.Current)
{
using (var transaction = session.BeginTransaction())
{
var categories = session.CreateCriteria(typeof(Category))
//.CreateCriteria("Articles").Add(NHibernate.Criterion.Restrictions.EqProperty("Category", "Id"))
.AddOrder(Order.Asc("Description"))
.List<Category>();
}
}
}
I am getting 7 Categories due to Left outer join used by Nhibernate
any idea what I am doing wrong in here?
Thanks
[Solution]
After a couple of hours reading nhibernate docs I here is what I came up with
var criteria = session.CreateCriteria(typeof (Category));
criteria.AddOrder(Order.Asc("Description"));
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
var cats1 = criteria.List<Category>();
Using Nhibernate linq provider
var linq = session.Linq<Category>();
linq.QueryOptions.RegisterCustomAction(c => c.SetResultTransformer(new DistinctRootEntityResultTransformer()));
var cats2 = linq.ToList();
I don't really know what's the problem, because I don't know how you save the categories, but it might be caused by using the wrong cascade setting in the mapping?
Using Join on a HasMany is unusual; it's typically used on References, the many side of a one-to-many relationship. Instead of the solution you came up with, you should lazy load the collection or use Fetch.Select. Both will cause NH to issue two selects, one to load the Category and another to load its associated Articles.
Addendum:
The error you're getting is pretty straight-forward: the collection can't be loaded because the ISession that was used to load the parent is out of scope (or its connection was closed). Setting the fetch mode to Select will resolve this (I think, I haven't tried it). So your collection mapping would be:
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Select();
If you can keep the ISession open I would recommend lazy loading:
HasMany(x => x.Articles).KeyColumn("CategoryId").LazyLoad();
It's unusual to use Join on a collection mapping due to the problem you ran into. Issuing a join from the one side will return a parent object for each object in the collection, just as it would in SQL.