Batch Update, Delete, Insert not work correctly with version control - nhibernate

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.**

Related

NHibernate vs Entity Framework 5 auto connecting entities

Recently I've come back to the question NHibernate vs EF5 for the Enterprise Application.
I know many important differences, but this one is the most suprising for me.
Consider two classic entities, Customer and Order (1:n):
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
**public virtual ICollection<Order> Orders { get; set; }**
}
public class Order
{
public virtual Guid Id { get; set; }
public virtual Guid CustomerId { get; set; }
public virtual string Number { get; set; }
public virtual DateTime Date { get; set; }
**public virtual Customer Customer { get; set; }**
}
For both NHibernate and EF5 there are two-way mappings.
Code snippets for loading all customers AND all Orders in context of DbContext and Session for EF5 and NHibernate accordingly:
using (TestOrmForDalDbEntities context = new TestOrmForDalDbEntities())
{
context.Configuration.ProxyCreationEnabled = false;
IQueryable<Customer> customers = context.Customers;
customers.Load();
IQueryable<Order> orders = context.Orders;
orders.Load();
}
using (ISession session = _sessionFactory.OpenSession())
{
var customers = session.Query<Customer>().ToList();
var orders = session.Query<Order>().ToList();
}
The result is:
EF5: Each Customer has a collection of appropriate Orders (EF5 automatically connect them)
NHibernate: None of Customers has a collection of appropriate Orders. Even though each Order HAS link to an appropriate Customer.
The question is: is it an NHibernate idea not to connect in such way or it's me who doesn't know how to configure NHibernate?
P.S. Mappings for NHibernate:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id).UnsavedValue(Guid.Empty).GeneratedBy.Guid();
Map(x => x.Name);
HasMany(x => x.Orders).Cascade.All();
}
}
public OrderMap()
{
Id(x => x.Id).UnsavedValue(Guid.Empty).GeneratedBy.Guid();
Map(x => x.Number);
Map(x => x.Date);
Map(x => x.CustomerId);
References(x => x.Customer);
}
The relation from Customers to Orders (HasMany) should be Inverse, meaning, it's the Order endpoint that has the foreign key to the Customer.

LINQ-to-NHibernate: Cannot use Linq Skip() and Take() with FetchMany

I have these entities:
public class BlogPost {
public virtual int Id { get; set; }
public virtual IList<Keyword> Keywords { get; set; }
public virtual IList<BlogComment> Comments { get; set; }
}
public class BlogComment {
public virtual int Id { get; set; }
public virtual BlogPost Post { get; set; }
}
public class Keyword {
public virtual int Id { get; set; }
public virtual IList<BlogPost> BlogPosts { get; set; }
}
I want to load a paged-list of BlogPosts by their Keywords and comments-count. So I try this:
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
But the folowing error occurs:
Specified method is not supported.
And when I remove .Skip((pageNumber - 1) * pageSize).Take(pageSize) it works! e.g.
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
// remove the below line
//.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
Have you any idea please to take a number of rows by including Keywords? Thanks for any suggestion.
I'm using NHibernate 3.2 mapping by code.
The problem is that the nhibernate linq provider isn't fully implemented yet.
You could move the skip / take calls to be after the ToList() but then you're going to be filtering on the entire result set rather than querying specifically for the records matching that range.
Alternatively you could use the QueryOver<> api which has proper support for Take and Skip as per this answer: https://stackoverflow.com/a/5073510/493
This should now be supported in 3.3.3.GA
http://sourceforge.net/p/nhibernate/news/2013/03/nhiberate-333ga-released/

NHibernate : update an ISet collection

See the class and mapping below. I'd like in some case update the address (at this time, it's all the time one address).
I do this :
var customer = session.Get<Customer>(customerId);
customer.Address.Clear();
customer.Address.Add(address);
address is coming from a form, the id field is not = 0 (when 0, at creation, no problem)
but when I do this :
session.Save(customer);
session.Commit();
I receive an exception on the commit (14 is the id of CustomerAddress) :
a different object with the same identifier value was already associated with the session: 14, of entity: CustomerAddress
What is the way to update this address ?
Thanks,
Class and Mapping
public class Customer
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual Iesi.Collections.Generic.ISet<CustomerAddress> Address { get; set; }
public Customer()
{
Address = new Iesi.Collections.Generic.HashedSet<CustomerAddress>();
}
}
public class CustomerAddress
{
public virtual int Id { get; set; }
public virtual string Street { get; set; }
public virtual Customer Customer { get; set; }
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.LastName)
.Length(50)
.Not.Nullable();
HasMany(x => x.Address)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan()
.Not.LazyLoad();
}
}
public class CustomerAddressMap : ClassMap<CustomerAddress>
{
public CustomerAddressMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Street).Length(50);
References(x => x.Customer);
}
}
If the address you are saving already has an id assigned to it you can simply do the following:
var customer = session.Get<Customer>(customerId);
Session.Merge(address); // This will copy your transient entity into
// ..an entity with the same id that is in the
// ..first level cache.
session.SaveOrUpdate(customer);
You are getting that error because and address with an ID of 14 is already associated with your NHibernate session. So when you create the new detached transient entity and try to save it with that id NHibernate throws an error. Generally this is a good thing as it is very rare to want to do what you are doing.
A far better pattern would be to have a ViewModel for the fields of an address that are changeable by a user, and then do the following:
var address = Session.Get<Address>(addressVM.Id);
Mapper.Map(addressVM, address); // This is some type of mapper to copy properties
// ..from one object to another. I like automapper
// ..for this
Session.SaveOrUpdate(address);
If you are updating an address, why are you clearing the collection and re-adding?
using (var tx = session.BeginTransaction())
{
var customer = session.Get<Customer>(customerId);
var address = customer.Address.Single(/*optional condition here*/);
//or, if you are not updating the Customer, this might be better
var address = session.Get<Address>(addressId);
address.Street = updatedStreetInfo;
tx.Commit();
}

Fluent NHibernate one to many not saving children

I am using Fluent NHibernate. This is a classic case of a one to many relationship. I have one Supply parent with many SupplyAmount children.
The Supply parent object is saving with correct info, but the amounts are not getting inserted into the db when I save the parent. What am I doing for the cascade not to work?
The entities are as follows:
public class Supply : BaseEntity
{
public Guid SupplyId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Comments { get; set; }
public virtual IList<SupplyAmount> Amounts { get; set; }
public Supply()
{
Amounts = new List<SupplyAmount>();
}
public virtual void AddAmount(SupplyAmount amount)
{
amount.Supply = this;
Amounts.Add(amount);
}
}
public class SupplyAmount : BaseEntity
{
public virtual Guid SupplymountId { get; set; }
public virtual Supply Supply { get; set; }
public virtual int Amount { get; set; }
}
And the mapping as follows:
public class SupplyMap : ClassMap<Supply>
{
public SupplyMap()
{
Id(x => x.SupplyId);
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.Comments);
HasMany<SupplyAmount>(x => x.Amounts)
.Inverse().Cascade.SaveUpdate()
.KeyColumn("SupplyAmountId")
.AsBag();
}
}
public class SupplyAmountMap : ClassMap<SupplyAmount>
{
public SupplyAmountMap()
{
Id(x => x.SupplyAmountId);
References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Map(x => x.Amount);
}
}
And this is how I call it:
public SaveIt()
{
Supply sOrder = Supply();
sOrder.FirstName = "TestFirst";
sOrder.LastName = "TestLast";
sOrder.Comments = "TestComments";
for (int i = 0; i < 5; i++)
{
SupplyAmount amount = new SupplyAmount();
amount.Amount = 50;
amount.Supply = sOrder;
sOrder.AddAmount(amount);
}
// This call saves the Supply to the Supply table but none of the Amounts
// to the SupplyAmount table.
AddSupplyOrder(sOrder);
}
I know this is an old post but why not...
// This call saves the Supply to the Supply table but none of the Amounts
This comment in SaveIt() indicates you call the save on the Supply and not the amounts.
In this case you have your logic the wrong way around.
So to fix this:
SupplyMap -> The Inverse shouldn't be there for Amounts.
HasMany<SupplyAmount>(x => x.Amounts).Cascade.SaveUpdate();
SupplyAmountMap ->
remove References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Replace it with
References<Supply>(x=>x.Supply);
You should now be right to call the save on your supply object only and it will cascade down to the amounts.
Session.Save(supply);
In your test after you have arrange the supply and supplyamount make sure you call a
Session.Flush()
after your save to force it in.
This isn't as important in code as you will usually run in transactions before recalling the supply object.
Cheers,
Choco
Also as a side note it usually not a good idea to be to verbose with fluentmappings. let the default stuff do it thing which is why I would recommend against the column naming hints.

get property count with entity using nhibernate

can I am hoping someone can point me to the right direction on how to get count of a property and the entity using a single trip to sql.
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public virtual int ArticlesCount { 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 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();
Cache.ReadWrite();
}
}
My goal is to get the all Categories and the count of the associated articles if there is any.
I have tried this
ICriteria crit = session.CreateCriteria(typeof(Category));
crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Description"), "Description")
.Add(Projections.Count("Articles"), "ArticlesCount"));
crit.SetResultTransformer(Transformers.AliasToBean (typeof(Category)));
var aa=crit.List();
unfortunately the generated sql shows the count of the Category table not the Articles list.
Thanks
You could use a multi-query, multiple sql statements but it is one trip to the database.
Here is an example from the nhibernate documentation:
https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/performance.html
IMultiQuery multiQuery = s.CreateMultiQuery()
.Add(s.CreateQuery("from Item i where i.Id > ?")
.SetInt32(0, 50).SetFirstResult(10))
.Add(s.CreateQuery("select count(*) from Item i where i.Id > ?")
.SetInt32(0, 50));
IList results = multiQuery.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Maybe not exactly what you were thinking.