We are not using Fluent Nhibernate. We have a table with a blob column which we would like to load conditionally. We already have specified it as 'lazy' and lazy loading is working fine. Is there a way to not load data for that column at all on a certain condition ? (hence for the property in respective class)
For example, here is the mapping
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Blah.blah.blah" assembly="Blah.blah">
<class name="MyDocument" table="HEAVY_DOCS">
<id name="Id" column="docnum" type="decimal"> </id>
<property name="docname" column="docname" />
<property name="details" column="details" />
<property name="doc_data" column="document_data" lazy="true" />
</class>
</hibernate-mapping>
and below is the code getting document
private IList<MyDocument> FetchDocuments(IList<string> docId, bool laodData)
{
if(!laodData)
{
docs = (from doc in Session.Query<MyDocument>()
where docId.Contains(doc.docnum)
select doc).ToList();
}
else
{
//if laodData is false dont load column document_data
}
return docs ;
}
The way, how to drive column selection by our code, is to use projections. Adjusted code could look like this:
if(!laodData)
{
docs = (from doc in Session.Query<MyDocument>()
where docId.Contains(doc.docnum)
select doc // complete object
).ToList();
}
else
{
docs = (from doc in Session.Query<MyDocument>()
where docId.Contains(doc.docnum)
select new MyDocument // in NHibernate world
{ // that would be called
docname= doc.docname, // projection
details = doc.details,
// no "doc_data" for LARGE document
Id = doc.Id,
}
).ToList();
}
With QueryOver the projections can profit from NHibernate built in powerful Result Transformers, which can fill even protected properties. Maybe see this custom transformer example for deep (relations including) transformations of QueryOver projections...
Related
I just want to understand what is the use of SerializationFeature.WRAP_ROOT_VALUE.
I have actually tried disabling the SerializationFeature.WRAP_ROOT_VALUE and for the class I have annotated with xmlRootElement. Here in this case after disabling the SerializationFeature.WRAP_ROOT_VALUE still after serializing I am getting the root value. To just avoid the root value I have to use the xmlType.
So trying to understand then what is the use of SerializationFeature.WRAP_ROOT_VALUE?
Sample code which I have tried
#XmlRootElement(name="person")
Public class Person {
#XmlElement(name = "insert")
private int insert;
#XmlElement(name = "update")
private int update;
}
The above is the POJO class which I was trying to serialize and also I have used
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
So with the above code the output is
"person" {
"insert" : 1,
"update" : 0
}
In the same case if I try to change the xmlRootElement to XmlType in Person class the output is
{
"insert" : 1,
"update" : 0
}
So I am confused like what is the use of SerializationFeature.WRAP_ROOT_VALUE if it is not giving the expected output?
I am using the Jackson version of 2.9.6
After digging more into this found that with the help of CXF I was able to solve this by adding small config in applicationcontext.xml file
<bean class="org.apache.cxf.jaxrs.provider.json.JSONProvider">
<property name="dropRootElement" value="true" />
</bean>
I'm working on a study project, implementing an ERP Webapplication with ZF2 and Doctrine.
Now I have a problem when I try to persist an entity.
It fails with this message:
Catchable fatal error: Object of class Application\Entity\Scope could not be converted to string in ...\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php on line 2791
I found a similar Question with this message, but the answer didn't help me:
Doctrine: Object of class User could not be converted to string
I'm trying to persist the entity Product which has a relation to ScopedName and this entity has a relation to the Scope entity.
When I add a __toString() method to the Scope entity it saves everything correctly, so I'm guessing the problem is not in my form or controller. If my mapping are ok I would guess that its a Doctrine Bug that does not support composite primary keys correctly. As you can see below, my entity ScopedName has a composite key.
These are my relevant doctrine mappings
Scope
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Application\Entity\Scope" table="application_scope">
<id name="id" type="integer">
<generator strategy="AUTO"/>
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
<field name="code" type="string" />
<unique-constraints>
<unique-constraint columns="code" />
</unique-constraints>
</entity>
</doctrine-mapping>
ScopedName
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Catalog\Entity\Product\ScopedName" table="catalog_product_scoped_name">
<id name="scope" column="scope_id" association-key="true" />
<id name="product" column="product_id" association-key="true" />
<field name="value" type="string" />
<many-to-one field="scope" target-entity="Application\Entity\Scope">
<join-column name="scope_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE" nullable="false" />
<cascade>
<cascade-persist />
</cascade>
</many-to-one>
<many-to-one field="product" target-entity="Catalog\Entity\Product" inversed-by="scopedNames" nullable="false">
<join-column name="product_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE" />
<cascade>
<cascade-persist />
</cascade>
</many-to-one>
</entity>
</doctrine-mapping>
Update
Is it maybe because I have no one-to-many in my Scope mapping? But I only need a unidirectional association here, because my Product entity will get more Associations like ScopedSku, ScopedDescription, ... and I don't want to define them all in my Scope mapping as this in another module and shouldn't know of all these ...
Update 2
Ok, I tried to add one-to-many to the other side, but didn't help either. Does someone have a clue why I get this error?
Shame on myself. It was my own failure in my controller, sorry for this.
My editAction looks like this ... commented lines were causing this error
public function editAction() {
$id = $this->params()->fromRoute('id');
$form = $this->getServiceLocator()->get('FormElementManager')->get('\Catalog\Form\ProductForm');
$form->addAdditional();
$product = $this->getEntityManager()->find('Catalog\Entity\Product', $id);
$productOld = clone($product);
$form->bind($product);
if($this->request->isPost()) {
$form->setData($this->request->getPost());
if($form->isValid()) {
foreach($product->getScopedNames() as $scopedName) {
$scopedName->setProduct($product);
//next line was the failure
//$scopedName->setScope($this->getEntityManager()->getReference('Application\Entity\Scope', $scopedName->getScope()));
//$this->getEntityManager()->persist($scopedName);
}
foreach($productOld->getScopedNames() as $scopedName) {
if(!$product->getScopedNames()->contains($scopedName)) {
$this->getEntityManager()->remove($scopedName);
}
}
$this->getEntityManager()->persist($product);
$this->getEntityManager()->flush();
$this->flashMessenger()->addSuccessMessage("Product has been saved successfully.");
$this->redirect()->toRoute('catalog/product');
} else {
$this->flashMessenger()->addErrorMessage("Form invalid");
}
} else {
$form->bind($product);
}
return array(
'form' => $form,
'id' => $id
);
}
I needed these lines before I made use of the DoctrineObject Hydrator. Now it works automatically. But I still need the line before and the next foreach in order to set the product itself to the relation and to remove old relations that aren't contained in the new post request.
Maybe someone has a better solution for this as well?
After running SQL Profiler, I realized that NHibernate was mapping strings to nvarchar(4000). I fixed it by specifying type=AnsiString and length=... in the hbm file. It is now generating varchar(8000) statements, and it is ignoring the length. How come?!
hbm file:
<property name="EmailAddress" column="EMAIL_ADDRESS" type="AnsiString" length="120" />
database field:
[EMAIL_ADDRESS] [varchar](120) NULL,
TIA
Actually in previous versions of nhibernate the check against length was implemented also for query creation.
But, the current implementation of the SqlDriver got a change last year,
see https://nhibernate.jira.com/browse/NH-3036 for details of the fix.
Bugfix:
// Used from SqlServerCeDriver as well
public static void SetVariableLengthParameterSize(IDbDataParameter dbParam, SqlType sqlType)
{
SetDefaultParameterSize(dbParam, sqlType);
// no longer override the defaults using data from SqlType, since LIKE expressions needs larger columns
// https://nhibernate.jira.com/browse/NH-3036
//if (sqlType.LengthDefined && !IsText(dbParam, sqlType) && !IsBlob(dbParam, sqlType))
//{
// dbParam.Size = sqlType.Length;
//}
if (sqlType.PrecisionDefined)
{
dbParam.Precision = sqlType.Precision;
dbParam.Scale = sqlType.Scale;
}
}
The defaults are what you see (4000/8000) dependent on the data type...
I have a sql2008 db that has a column type of xml.
Using NHibernate 2.5 I can save to this column no problems.
I've dropped in the NHibernate 3.0 dlls and suddenly I'm getting the above errors?
My mapping file doesn't have a type against that column so surely NHibernate should pick up the xml data type (I'm using the sql 2008 dialect) ?
4000 just seems to be a suspicious length, i.e the length of a varchar column in sql.
I see there are a few articles about mapping xml columns using custom UserTypes, etc.
How come this just worked in 2.5 and now doesn't in 3.0 ? I don't need any special handling. That column gets used as a string everywhere.
The reason this behavior changed from 2.x to 3.0.0 was due to a code commit on NHibernate.Driver.SqlClientDriver, which effectively turned on some of the behavior of enabling the prepare_sql configuration in order to resolve an issue with the caching of query plans.
A C# string property mapped to a column that has no other type specified in its mapping will get treated as a NHibernate.SqlTypes.StringSqlType and given a 4000 character limit by the driver:
From NHibernate.SqlTypes.StringSqlType:
/// <remarks>
/// This can store the length of the string that the <see cref="IDbDataParameter"/> can hold.
/// If no value is provided for the length then the <c>Driver</c> is responsible for
/// setting the properties on the <see cref="IDbDataParameter"/> correctly.
/// </remarks>
So, you can see that the code below from the driver (NHibernate.Driver.SqlClientDriver), maps a default string property to a length of 4000 characters.
/* SNIP */
private const int MaxAnsiStringSize = 8000;
private const int MaxBinarySize = MaxAnsiStringSize;
private const int MaxStringSize = MaxAnsiStringSize / 2;
private const int MaxBinaryBlobSize = int.MaxValue;
private const int MaxStringClobSize = MaxBinaryBlobSize / 2;
/* SNIP */
private static void SetDefaultParameterSize(IDbDataParameter dbParam, SqlType sqlType)
{
switch (dbParam.DbType)
{
/* SNIP */
case DbType.String:
case DbType.StringFixedLength:
dbParam.Size = IsText(dbParam, sqlType) ? MaxStringClobSize : MaxStringSize;
break;
/* SNIP */
}
}
For configurations with prepare_sql set to false, NH 3.0.0 now brings the SetDefaultParameterSize method into play where it was not before
As you noted, you can use NH-3.0.0-native support for the SQL Server XML datatype (thanks to NH-866), like so:
<property name="Data" type="xml" not-null="true" />
or more explicitly, but equivalently:
<property name="Data" type="XmlDoc" not-null="true">
<column name="DATA" sql-type="XmlSql" />
</property>
But using NHibernate.Type.XmlDocType expects the property type to be of C# type XmlDocument - leading to a cast exception.
You could do the XmlDocument.InnerXml fix like you mentioned, but that's a lot of unnecessary transformation from string to doc and back. I've used the following mapping to keep the domain property a string:
<property name="Data" type="StringClob" not-null="true">
<column name="DATA" sql-type="XmlSql" />
</property>
Using NHibernate.Type.StringClobType will return true for the IsText(dbParam, sqlType) call in the driver snippet above, giving a max character length of int.MaxValue / 2 - something like 2GB of string data.
I solved this by changing the property type to xml in the mapping file.
Then just used the XmlDocument that is returned as XmlDocument.InnerXml to get the string value.
I'm having trouble saving an entity into an SQL Server 2005 database. I'm using NHibernate 2.0.0.3002 for my persistence layer. The mapping is typical, with an integer ID, as follows
<id name="Id" unsaved-value="0">
<column name="Id"/>
<generator class="identity" />
</id>
I've omitted the rest for brevity. The application is using a repository class with a generic save method as follows
public void Save(T toSave)
{
Save(new T[] { toSave });
}
public void Save(IEnumerable<T> toSave)
{
using (ISession session = SessionFactory.OpenSession())
{
foreach (T item in toSave)
{
session.SaveOrUpdate(item);
}
session.Flush();
}
}
When calling SaveOrUpdate on the session, an exception is thrown with a message of "null identifier". When I check the database the row has been inserted with all the correct values, so I think the problem is when NHibernate tries to set the Id property of the entity with the value returned by ##IDENTITY. I can see through SQL Profiler that ##IDENTITY is being called so I don't understand why the exception is thrown.
Has anyone else had this problem?
Both Save and Delete must happen in a transaction and the transaction must be committed at the end.
like so:
public void Save(IEnumerable<T> toSave)
{
using (ISession session = SessionFactory.OpenSession())
{
ITransaction transaction = Session.BeginTransaction();
foreach (T item in toSave)
{
session.SaveOrUpdate(item);
}
transaction.Commit();
session.Flush();
}
}
please note: you'll want to wrap that in a using and properly rollback... also, placement of where you're opening and committing the transaction will matter based on your scenario. You should also close the transaction when you're done...
Also, can you elaborate on where the exception is happening? It sounds like you're saving a parent and then the child is throwing because the parent's id is null? Or, is it actually throwing on saving the parent?
It is maybe helpfull to configure log4net so that you can log and view the actions that NHibernate is doing ...
I once had a problem with NHibernate using Access as well, and was able to solve it by setting up logging so that I could exactly pinpoint the cause of the problem.
The error-message that I received, was different from yours, but this is the article in where I describe how I solved my problem. Perhaps it could be helpfull for you to. :)
Identity is pretty discouraged by the NHibernate developers.. the main issue we ran into is that until you Flush you don't get an Id. In your Int case HiLo would be a good replacement.
That being said I think you actually want to do this...
<id name="Id" column="Id" unsaved-value="0">
<generator class="identity"/>
</id>
I also had this error and the solution was to wrap it in a transaction as Ben mentioned. I wasn't however, able to get Ben nor gilles27 code to work - probably because I'm new to generics and NHibernate. I created a slightly different implementation that works (using Fluent NHibernate v1.3):
public static ISession LocalDbSession = null;
public static void Save<T>(T toSave)
{
using (var transaction = LocalDbSession.BeginTransaction())
{
LocalDbSession.Save(toSave);
transaction.Commit();
LocalDbSession.Flush();
}
}
public static void Save<T>(IEnumerable<T> toSave)
{
using (var transaction = LocalDbSession.BeginTransaction())
{
foreach (T item in toSave)
{
LocalDbSession.Save(item);
}
transaction.Commit();
LocalDbSession.Flush();
}
}
i think instead of generator class="identitiy"/>
try generator class="guid.comp"