NHibernate: Cannot insert null exception when storing a new object - nhibernate

I'm trying a very basic example with NHibernate but get an exception on session.Save() call.
Here is my model:
public class Student
{
public virtual int ID { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstMidName { get; set; }
}
Here is Student.hbm.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemoApp" namespace = "NHibernateDemoApp">
<class name = "Student">
<id name = "ID">
<generator class = "native"/>
</id>
<property name = "LastName"/>
<property name = "FirstMidName"/>
</class>
</hibernate-mapping>
This the code to save an entity:
using (ISession session = this.sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
savedEntity = (Student) session.Save(new Student { ID = 1, FirstMidName = "Test first name", LastName = "Test Last name"});
try
{
transaction.Commit();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
if (!transaction.WasCommitted)
{
transaction.Rollback();
}
}
}
}
It fails with the following error:
Cannot insert the value NULL into column 'ID', table
'NHibernateDemoDB.dbo.Student'; column does not allow nulls. INSERT
fails.
What's wrong here?

I recreated my table with the following script and now it works:
CREATE TABLE [dbo].[Student] (
[ID] INT IDENTITY (1,1) NOT NULL,
[LastName] NVARCHAR (MAX) NULL,
[FirstMidName] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED ([ID] ASC)
)

Related

Incorrect Select Statement generated when using Guid as Primary Key in NHibernate 4

I use the following code in LinqPad to demonstrate the problem:
void Main()
{
Configuration cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.ConnectionString = #"Data Source=xxxxxxxx\SQLEXPRESS;Initial Catalog=Testarossa;Integrated Security=True";
db.Dialect<MsSql2012Dialect>();
});
/* Add the mapping we defined: */
var mapper = new ModelMapper();
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
cfg.AddMapping(mapping);
/* Create a session and execute a query: */
using (ISessionFactory factory = cfg.BuildSessionFactory())
using (ISession session = factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
session.Get<Person>(2).Dump();
session.Get<User>(new Guid("2952A2BB-DAFE-4221-A102-798A2EB12626")).Dump();
tx.Commit();
}
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
this.Table("person");
this.Id(p => p.Id);
this.Property(p => p.FirstName);
this.Property(p => p.LastName);
this.Property(p => p.BirthDate);
}
}
public class UserMap : ClassMapping<User>
{
public UserMap()
{
this.Table("user");
this.Id(p => p.Id, id =>
{
id.Type(NHibernateUtil.Guid);
});
this.Property(p => p.FirstName);
this.Property(p => p.LastName);
this.Property(p => p.Created);
}
}
public class Person
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime BirthDate { get; set; }
}
public class User
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime Created { get; set; }
}
The query for Person works well. The query for User produced the following SQL:
SELECT userquery_0_.Id as Id11_0_, userquery_0_.FirstName as FirstName11_0_,
userquery_0_.LastName as LastName11_0_,
userquery_0_.Created as Created11_0_
FROM user userquery_0_ WHERE userquery_0_.Id=?
With the following error message:
Incorrect syntax near the keyword 'user'.
Well, I understand the reason for the SQL error, what I do not understand is, why is NHibernate generating this incorrect SQL statement.
Can someone please help out and explain? Am I doing something wrong?
For convenience here the table ddl:
CREATE TABLE Testarossa.dbo.Person (
Id INT PRIMARY KEY NOT NULL,
FirstName NVARCHAR NOT NULL,
LastName NVARCHAR NOT NULL,
BirthDate DATETIME DEFAULT (getdate())
);
CREATE TABLE Testarossa.dbo.[User] (
Id UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,
FirstName NVARCHAR NOT NULL,
LastName NVARCHAR NOT NULL,
Created DATETIME DEFAULT (getdate())
);
The word/name user is a keyword (at least in SQL Server). So, such table name is ...
At least we should escape it like this
this.Table("[user]");
This style will work for SQL Server, general escape syntax for NHibernate is:
this.Table("`user`");

Batch Insert - Foreign Key Not Working

I'm trying to do a batch insert and it's not working. I thought I had this working but something seems to have broken and I'd appreciate it if someone could show me what.
Edit - Here's the database schema:
CREATE TABLE [dbo].[Categories](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY CLUSTERED ([Id])
)
CREATE TABLE [dbo].[ProductTopSellersCategory](
[ProductId] [int] NOT NULL,
[CategoryId] [int] NOT NULL,
[Order] [int] NOT NULL,
CONSTRAINT [PK_ProductTopSellersCategory]
PRIMARY KEY CLUSTERED ([ProductId], [CategoryId])
)
ALTER TABLE [dbo].[ProductTopSellersCategory] ADD
CONSTRAINT [FK_ProductTopSellersCategory_Products]
FOREIGN KEY ([ProductId]) REFERENCES [dbo].[Products] ([Id]),
CONSTRAINT [FK_ProductTopSellersCategory_Categories]
FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([Id])
I have the following entities:
public class Category {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class ProductTopSellerCategory {
public virtual ProductTopSellerCategoryIdentifier Id { get; set; }
private Product _product;
public virtual Product Product {
get { return _product; }
set { _product = value; Id.ProductId = _product.Id; }
}
private Category _category;
public virtual Category Category {
get { return _category; }
set { _category = value; Id.CategoryId = _category.Id; }
}
[Required]
public virtual int Order { get; set; }
public ProductTopSellerCategory() {
Id = new ProductTopSellerCategoryIdentifier();
}
}
[Serializable]
public class ProductTopSellerCategoryIdentifier {
public virtual int ProductId { get; set; }
public virtual int CategoryId { get; set; }
#region Composite Id Members
public override bool Equals(object obj) {
if (obj == null || !(obj is ProductTopSellerCategoryIdentifier))
return false;
var i = (ProductTopSellerCategoryIdentifier)obj;
return ProductId == i.ProductId && CategoryId == i.CategoryId;
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
return ProductId + "|" + CategoryId;
}
#endregion
}
With the corresponding fluent mappings:
public class CategoryMap : ClassMap<Category> {
public CategoryMap() {
Table("Categories");
Id(x => x.Id);
Map(x => x.Name);
}
}
public class ProductTopSellerCategoryMap : ClassMap<ProductTopSellerCategory> {
public ProductTopSellerCategoryMap() {
Table("ProductTopSellersCategory");
CompositeId(x => x.Id)
.KeyProperty(x => x.ProductId)
.KeyProperty(x => x.CategoryId);
References(x => x.Product).ReadOnly();
References(x => x.Category).ReadOnly();
Map(x => x.Order, "[Order]");
}
}
Now when I say:
var category = new Category() { Name = "Test 1" };
var product = session.Get<Product>(1);
var topSeller = new ProductTopSellerCategory() { Product = product, Category = category };
session.SaveOrUpdate(category);
session.SaveOrUpdate(topSeller);
session.Transaction.Commit();
It throws the error:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_ProductTopSellersCategory_Categories". The conflict occurred in
database "xxx", table "dbo.Categories", column 'Id'. The statement has
been terminated.
I've tried to simplify this example as much as possible. I'd really appreciate the help. Thanks
You have a one-to-many relationship between Category and ProductTopSellerCategory with just the many side mapped. Normally you would use the inverse attribute on the collection mapped on the one side but you don't have that mapped so I suggest:
using (var txn = session.BeginTransaction())
{
var category = new Category() { Name = "Test 1" };
session.Save(category);
session.Flush();
var product = session.Get<Product>(1);
var productTopSellerCategory = new ProductTopSellerCategory() { Product = product, Category = category };
session.Save(productTopSellerCategory);
txn.Commit();
}
The problem with your original code is that NHibernate is attempting to insert the new ProductTopSellerCategory then update the category. It's doing this because the inverse attribute is not set. Forcing NHibernate to insert the new Category by flushing the session should resolve the problem.
I think I've found a solution. It's a little bit of a hack but it meant I didn't have to change my entities and mappings. The issue happens because the CategoryId in the identity type doesn't point to the same reference as the Category.Id in the top sellers entity. To fix this issue I need to add the following just before I insert the top seller:
topSeller.Id.CategoryId = topSeller.Category.Id;

NHibernate mapping by code ManyToOne with CompositeIdentity

I am trying to convert my FluentNHibernate mappings to NHibernate Mapping By-code using NHibernate 3.3.3. The goal is to upgrade to NHibernate 3.3.3 and to cut down on the number of assemblies being distributed. I am having some trouble with translating FluentNHibernate's References mapping to a Many-To-One mapping.
Many of my entities have descriptions that need translations. For this I use a Texts table that contains these texts in all available languages. I use the text ID to reference the Texts table and then in the Data Access Object I filter the required language. This works create using NHibernate 3.1 and FluentNHibernate, with NHibernate 3.3.3 and mapping by-code however I just a MappingException saying : property mapping has wrong number of columns: Category.Description type: Text.
Where is my new mapping wrong? Or is this type of mapping not possible in NHibernate 3.3.3.
This is the Texts table (SQL-server 2008).
CREATE TABLE Texts (
ID int NOT NULL,
languageID nvarchar(10) NOT NULL,
Singular nvarchar(max) NOT NULL,
Plural nvarchar(max) NULL,
CONSTRAINT PK_Texts PRIMARY KEY CLUSTERED (ID ASC, languageID ASC)
WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
The Text class:
public class Text
{
public Text(int id, string language, string singular, string plural)
{
this.ID = new TextCompositeID(id, language);
this.Singular = singular;
this.Plural = plural;
}
public TextCompositeID ID { get; private set; }
public string Plural { get; private set; }
public string Singular { get; set; }
public override bool Equals(object obj)
{
var text = (Text)obj;
if (text == null)
{
return false;
}
return this.ID.Equals(text.ID);
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
As an example here is the Category class:
public class Category
{
public int ID { get; set; }
public Text Description { get; set; }
}
The FluentNHibernate xml mapping for the Category class looks like this:
<class xmlns="urn:nhibernate-mapping-2.2"
mutable="true"
name="Category"
lazy="false"
table="Category"
where="IsObsolete=0">
<id name="ID" type="System.Int32">
<column name="ID" not-null="true" />
<generator class="native" />
</id>
<many-to-one cascade="none"
class="Text"
name="Description">
<column name="TextID"
not-null="true"
unique="false" />
</many-to-one>
</class>
Which was generated from this:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
this.Table("Category");
Not.LazyLoad();
this.Where("IsObsolete=0");
Id(x => x.ID)
.Column("ID")
.GeneratedBy.Native()
.Not.Nullable();
References(x => x.Description)
.Column("DescriptionID")
.Cascade.None()
.Not.Unique()
.Not.Nullable();
}
}
This is the NHibernate ClassMapping I created:
public CategoryMap()
{
this.Lazy(false);
this.Mutable(true);
this.Table("Category");
this.Where("IsObsolete=0");
this.Id(
x => x.ID,
map =>
{
map.Column("ID");
map.Generator(Generators.Native);
});
this.ManyToOne(
x => x.Description,
map =>
{
map.Cascade(Cascade.None);
map.Class(typeof(Text));
map.Column("TextID");
map.Fetch(FetchKind.Join);
map.Lazy(LazyRelation.NoLazy);
map.ForeignKey("none");
});
}
From this I get this xml mapping:
<class name="Category"
lazy="false"
table="Category"
where="IsObsolete=0">
<id name="ID"
column="ID"
type="Int32">
<generator class="native" />
</id>
<many-to-one name="Description"
class="Text"
column="TextID"
fetch="join"
foreign-key="none"
lazy="false" />
</class>
I found an answer myself. I had to remove the composite ID from the Text class:
public class Text
{
public Text(int id, string language, string singular, string plural)
{
this.ID = id;
this.LanguageID = language;
this.Singular = singular;
this.Plural = plural;
}
public int ID { get; private set; }
public string LanguageID { get; private set; }
public string Plural { get; private set; }
public string Singular { get; set; }
public override bool Equals(object obj)
{
var text = (Text)obj;
if (text == null)
{
return false;
}
return this.ID.Equals(text.ID);
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
The Category mapping has become:
public CategoryMap()
{
this.Lazy(false);
this.Mutable(true);
this.Table("Category");
this.Where("IsObsolete=0");
this.Id(
x => x.ID,
map =>
{
map.Column("ID");
map.Generator(Generators.Native);
});
this.ManyToOne(
x => x.Description,
map =>
{
map.Column("TextID");
map.Fetch(FetchKind.Join);
map.ForeignKey("none");
map.Lazy(LazyRelation.NoLazy);
});
}
In the Data Access Object the old QueryOver query now gets me the required result.

Insert in two related table at once with Nhibernate

I have two entities like
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int DataId { get; set; }
}
public class Data
{
public int DataId { get; set; }
public string details { get; set; }
public int PersnId{ get; set; }
}
as you see both table are relate to each other. I want a solution to insert data in both table at once. I 1-insert person, 2-insert data and then update person and it works but I'm looking for way to eliminate Update.
My mapping for person table:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="TestNhibrinate" assembly="TestNhibrinate">
<class name="TestNhibrinate.Entites.Person" table="Person" lazy="false">
<id name="PersonId" column="PersonId" type="int" >
<generator class="identity" />
</id>
<property name="Name" column="Name" type="String" length="50" />
<many-to-one name="Adress" class="TestNhibrinate.Entites.Adress" column="AdressId"/>
</class>
</hibernate-mapping>
and same mapping for data.
You entities should look like this:
public class Person
{
public Person()
{
DataCollection = new List<Data>();
}
public int PersonId { get; set; }
public string Name { get; set; }
public int DataId { get; set; }
public IList<Data> DataCollection{get;set;}
public void AddData(Data item)
{
if(!DataCollection.Contains(item))
{
DataCollection.Add(item);
}
}
}
public class Data
{
public int DataId { get; set; }
public string details { get; set; }
public Person Person{ get; set; }
}
This way you create a one-to-many relation from Person to Data. If you save your person entity after you added some data, the data will also be persisted. This depends on your cascade options offcourse.
I'm not sure how to map this with XML mappings, since i always use Fluent or Auto mappings.

Best way to implement Entity with translatable properties in NHibernate

Consider the following class (simplified in order to focus in the core problem):
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
and tables:
Question
- QuestionId ((primary key, identity column and key)
- Code
QuestionTranslation
- QuestionTranslationId (primary key, identity column; not really relevant to the association)
- QuestionId (composite key element 1)
- CultureName (composite key element 2) (sample value: en-US, en-CA, es-ES)
- Text
- Hint
How I can map the Question class so the Text and Hint properties are populated using the current thread's culture. If the thread's culture is changed I would like the Text and Hint properties to automatically return the appropriate value without the need for the Question entity to be reloaded.
Note that I'm only outlining the relevant class and properties from the business side. I'm totally open to any new class or property needed to achieve the desired functionality.
An alternative to Firo's answer (yes, I copied it and adapted it and feel bad about this).
It uses a dictionary and maps the translations as composite element (so it doesn't need the id at all)
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text
{
get
{
var translation = Translations[CultureInfo.CurrentCulture.Name];
if (translation != null) return translation.Text
return null;
}
set
{
GetTranslation(CultureInfo.CurrentCulture.Name).Text = value;
}
}
public virtual string Hint
{
get
{
var translation = Translations[CultureInfo.CurrentCulture.Name];
if (translation != null) return translation.Hint
return null;
}
set
{
GetTranslation(CultureInfo.CurrentCulture.Name).Hint = value;
}
}
private QuestionTranslation GetTranslation(CultureInfo.CurrentCulture.Name)
{
QuestionTranslation translation;
if (!Translations.TryGetValue(CultureInfo.CurrentCulture.Name, out translation))
{
translation = new QuestionTranslation()
Translations[CultureInfo.CurrentCulture.Name] = translation;
}
return translation;
}
protected virtual IDictionary<string, QuestionTranslation> Translations { get; private set; }
}
class QuestionTranslation
{
// no id, culture name
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
mapping:
<class name="Question">
<id name="QuestionId" column="QuestionId"/>
<map name="Translations" table="QuestionTranslation" lazy="true">
<key column="QuestionId"/>
<index column="CultureName"/>
<composite-element class="QuestionTranslation">
<property name="Text"/>
<property name="Hint"/>
</composite-element>
</bag>
</class>
Edited to reflect changed answer:
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text
{
get
{
var currentculture = CultureInfo.CurrentCulture.Name;
return Translations
.Where(trans => trans.CultureName == currentculture)
.Select(trans => trans.Text)
.FirstOrDefault();
}
set
{
var currentculture = CultureInfo.CurrentCulture.Name;
var translation = Translations
.Where(trans => trans.CultureName == currentculture)
.FirstOrDefault();
if (translation == null)
{
translation = new QuestionTranslation();
Translations.Add(translation);
}
translation.Text = value;
}
}
public virtual string Hint
{
get
{
var currentculture = CultureInfo.CurrentCulture.Name;
return Translations
.Where(trans => trans.CultureName == currentculture)
.Select(trans => trans.Hint)
.FirstOrDefault();
}
set
{
var currentculture = CultureInfo.CurrentCulture.Name;
var translation = Translations
.Where(trans => trans.CultureName == currentculture)
.FirstOrDefault();
if (translation == null)
{
translation = new QuestionTranslation();
Translations.Add(translation);
}
translation.Hint = value;
}
}
protected virtual ICollection<QuestionTranslation> Translations { get; set; }
}
class QuestionTranslation
{
public virtual int Id { get; protected set; }
public virtual string CultureName { get; set; }
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
<class name="Question" xmlns="urn:nhibernate-mapping-2.2">
<id name="QuestionId" column="QuestionId"/>
<bag name="Translations" table="QuestionTranslation" lazy="true">
<key>
<column name="QuestionId"/>
</key>
<one-to-many class="QuestionTranslation"/>
</bag>
</class>
<class name="QuestionTranslation" table="QuestionTranslation" xmlns="urn:nhibernate-mapping-2.2">
<id name="QuestionTranslationId"/>
<many-to-one name="ParentQuestion" column="QuestionId"/>
</class>
if you have a lot of translations then change ICollection<QuestionTranslation> Translations { get; set; } to IDictionary<string, QuestionTranslation> Translations { get; set; } and map as <map> but normally the above should do it