in fluent-nhibernate,saving a many-to-one entity, why i should give a version to the referenced entity - fluent-nhibernate

i have the following entities:
public class Worker
{
public int WorkerID {get;set;}
public string Name { get;set;}
public int version { get;set;}
}
public class TransferOrder
{
public int TransferOrderID { get;set;}
public Worker workerTobeTransfered{get;set;}
public int version { get;set;}
}
and i am using the Auto mapping, in fluent nhibernate.
when i try to save the TransferOrder like this:
TransferOrder order = new TransferOrder();
order.Worker = new Worker(){WorkerID = 1};
Session.Save(order);
but in database, the workerID in the TransferOrder table is NULL???
but when i give a version to the worker, it is saved as normal?
TransferOrder order = new TransferOrder();
order.Worker = new Worker(){WorkerID = 1,Version = 1};
Session.Save(order);
notice that it is not important what version number is given to the worker as long as it is not 0.
and i have a worker saved in the database with workerID = 1.
how can i handle this ? why should i give a version to the worker???is the nhibernate making sure that worker is saved?? and why it should do that ?

Maybe this is the scenerio:
Your Work.WorkerID is an identity column within your Work table and, you cannot insert a row into your table that consists of only an identity column entry.
But when you provide a value for Work.Version, as well, then you are creating a valid insert.

The "version" is probably going to be auto mapped to a NHibernate attribute for optimistic concurrency checking. I would use Fluent NHibernate to generate the mapping XML to see what it's using, as I can't find anything via Google about the default settings for auto mapped .

Related

EF Core 3.0 Reversing SQL Insert Statements when using DbSet.Add method [duplicate]

Until EF core version used in .donet core 2.2, after the .Add command, EF fills the key column with a big negative number.
After 3.0 upgrade this does not happens anymore.
Here is the code:
var appointment = new Appointment
{
Date = DateTime.Today,
ProfessionalId = schedule.ProfessionalId
};
await service.AddAsync(appointment);
string message = null;
if (service.AddLastPrescription(appointment.Id, schedule.PacienteId))
....
The problem is that now the "appointment.Id" is zero and the call to the service function will fail (FK error).
This behavior was expected in 3.0?
update
AddAsync function
private DbSet<T> dbSet;
public async Task AddAsync(T t)
{
await dbSet.AddAsync(t);
}
where T is ModelBase:
public class ModelBase
{
[Key]
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
This behavior was expected in 3.0?
Yes, it is one of the 3.0 Breaking Changes - Temporary key values are no longer set onto entity instances.
The proposed solutions there are:
Not using store-generated keys.
Setting navigation properties to form relationships instead of setting foreign key values.
Obtain the actual temporary key values from the entity's tracking information. For example, context.Entry(blog).Property(e => e.Id).CurrentValue will return the temporary value even though blog.Id itself hasn't been set.
Option #1 doesn't make sense (apparently the affected places already use store generated keys).
Option #2 is preferable if you have navigation properties.
Option #3 is closer to the previous behavior, but requires access to the db context.

NHibernate Proxy Id Value

Seeing a scenario where upon loading an entity by id, NH is loading via proxy (which is expected), however, backing fields for id = 0, but virtual property (getter), is the id of the entity from the db.
Example:
private int _id;
public virtual int Id { get { return _id; } }
and mapping
...
<id name="Id" access="nosetter.camelcase-underscore">
...
Upon loading the entity, the _id = 0, but Id = 4 for example.
Is it normal behaviour for the backing fields to not be initialised while entity is proxied?
I would say: do not worry. There is nothing wrong. And it is normal behaviour.
You can try to create another property, e.g.
public virtual int MyTestId
{
get { return _id + 1; }
}
And you'll see that MyTestId is 5 (to continue the case study above, when the Id == 4). Simply, you've most likely faced the VS debugger... which is just a human.

NHibernate query by Transient instance results in "save the transient instance"-exception

I have some old code which is performing a query where a model can be transient. That is, a model with some fields populated from user input, which are then used as part of the query.
It worked under NH 2.1.x, but is failing under the latest version.
The exception raised is "object references an unsaved transient instance - save the transient instance before flushing". This happens when NH attempts to perform a query using a non-persisted object as part of the query.
A simplified version to illustrate the problem.
abstract class BaseModel
public virtual long Id { get; set; }
class Car : BaseModel
public virtual Engine Engine { get;set; }
class Engine : BaseModel
public virtual string Kind { get; set; }
public static IList<Car> GetByEngine(Engine eng) {
ICriteria c = Session.CreateCriteria<Car>();
c.Add(Expression.Eq("Engine", eng));
return c.List<Car>(); // <--- Error occurs here
}
And calling code is equivalent to this:
Engine obj = new Engine { Id = 42 }; // Transient instance
var x = GetByEngine(obj);
What I expected to happen (Which appears to be the behaviour of the old NHibernate version), is that the Engine passed is used only for getting the Id. That is, generating SQl like
select .... from Cars where Engine = 42
But with the new version NHibernate seems to check that the engine used in the Expression is actually persisted.
Is there a way to avoid having to load a persisted Engine before performing the query ?
yes using Session.Load() which returns the object if already in the session or a lazyLoadingProxy if not present.
public static IList<Car> GetByEngine(Engine eng) {
ICriteria c = Session.CreateCriteria<Car>();
c.Add(Expression.Eq("Engine", Session.Load<Engine>(eng.Id)));
return c.List<Car>();
}
You can use the Session.Load method, which exist for this kind of scenarios.
The Load method will return a Proxy to the Entity and won't hit the Data Base untill you access one of it's properties, (except the Primary key property which won't hit the DB at all).
Usage:
Engine obj = session.Load<Engine>(42);
var x = GetByEngine(obj);
check this article about Session.Get and Session.Load
I think you could do something like this:
public static IList<Car> GetByEngine(Engine eng) {
ICriteria c = Session.CreateCriteria<Car>().CreateCriteria("Engine");
c.Add(Expression.Eq("Id", eng.Id));
return c.List<Car>();
}
Anyway... how it's possible that a car with that engine exists if you haven't saved it yet?

StaleStateException when changing id in nhibernate

Very new to NHibernate so I expect there will be a quick fix here.
The code I'm looking at has an unorthodox design.
public class Partner
{
public virtual int Id { get; set;} //note the set is not private
public virtual String Name { get; set;}
}
and is mapped with FluentNhibernate's Automappings
When Partner is created its Id is 0 but when
I call Flush to persist to the db:
CurrentSession.SaveOrUpdate(obj);
CurrentSession.Flush();
I get a Unexpected row count: 0; expected: 1 StaleStateException
I'm assuming its because NHibernate doesn't like me changing the Primary Key, Id.
In this case I need to. How can I configure NH to let me achieve this abomination?
You can do the update with HQL:
session.CreateQuery("update Partner set Id = :newId where Id = :oldId")
.SetParameter("newId", newId)
.SetParameter("oldId", oldId)
.ExecuteUpdate();
I suggest that you Evict the entity from the session, or use a stateless one if you are going to update the Id.

How do you automap List<float> or float[] with Fluent NHibernate?

Having successfully gotten a sample program working, I'm now starting
to do Real Work with Fluent NHibernate - trying to use Automapping on my project's class
heirarchy.
It's a scientific instrumentation application, and the classes I'm
mapping have several properties that are arrays of floats e.g.
private float[] _rawY;
public virtual float[] RawY
{
get
{
return _rawY;
}
set
{
_rawY = value;
}
}
These arrays can contain a maximum of 500 values.
I didn't expect Automapping to work on arrays, but tried it anyway,
with some success at first. Each array was auto mapped to a BLOB
(using SQLite), which seemed like a viable solution.
The first problem came when I tried to call SaveOrUpdate on the
objects containing the arrays - I got "No persister for float[]"
exceptions.
So my next thought was to convert all my arrays into ILists e.g.
public virtual IList<float> RawY { get; set; }
But now I get:
NHibernate.MappingException: Association references unmapped class: System.Single
Since Automapping can deal with lists of complex objects, it never
occured to me it would not be able to map lists of basic types. But
after doing some Googling for a solution, this seems to be the case.
Some people seem to have solved the problem, but the sample code I
saw requires more knowledge of NHibernate than I have right now - I
didn't understand it.
Questions:
1. How can I make this work with Automapping?
2. Also, is it better to use arrays or lists for this application?
I can modify my app to use either if necessary (though I prefer
lists).
Edit:
I've studied the code in Mapping Collection of Strings, and I see there is test code in the source that sets up an IList of strings, e.g.
public virtual IList<string> ListOfSimpleChildren { get; set; }
[Test]
public void CanSetAsElement()
{
new MappingTester<OneToManyTarget>()
.ForMapping(m => m.HasMany(x => x.ListOfSimpleChildren).Element("columnName"))
.Element("class/bag/element").Exists();
}
so this must be possible using pure Automapping, but I've had zero luck getting anything to work, probably because I don't have the requisite knowlege of manually mapping with NHibernate.
Starting to think I'm going to have to hack this (by encoding the array of floats as a single string, or creating a class that contains a single float which I then aggregate into my lists), unless someone can tell me how to do it properly.
End Edit
Here's my CreateSessionFactory method, if that helps formulate a
reply...
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<DlsAppOverlordExportRunData>()
.Where(t => t.Namespace == "DlsAppAutomapped")
.Conventions.Add( DefaultCascade.All() )
;
sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile)
.ShowSql()
)
.Mappings(m => m.AutoMappings.Add(autoPersistenceModel)
.ExportTo(autoMapExportDir)
)
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory()
;
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return sessionFactory;
}
I would probably do a one to many relationship and make the list another table...
But maybe you need to rethink your object, is there also a RawX that you could compose into a RawPoint? This would make a table with 3 columns (ParentID, X, Y).
The discontinuity comes from wanting to map a List to a value that in an RDBMS won't go in a column very neatly. A table is really the method that they use to store Lists of data.
This is the whole point of using an ORM like NHibernate. When doing all the querying and SQL composition by hand in your application, adding a table had a high cost in maintenance and implementation. With NHibernate the cost is nearly 0, so take advantage of the strengths of the RDBMS and let NHibernate abstract the ugliness away.
I see your problem with mapping the array, try it with an override mapping first and see if it will work, then you could maybe create a convention override if you want the automap to work.
.Override<MyType>(map =>
{
map.HasMany(x => x.RawY).AsList();
})
Not sure if that will work, I need to get an nHibernate testing setup configured for this stuff.
Since I posted my question, the Fluent NHibernate team have fixed this problem.
You can now automap ILists of C# value types (strings, ints, floats, etc).
Just make sure you have a recent version of FNH.
Edit
I recently upgraded from FNH 1.0 to FNH 1.3.
This version will also automap arrays - float[], int[], etc.
Seems to map them as BLOBs. I assume this will be more efficient than ILists, but have not done any profiling to confirm.
I eventually got an override to work - see the end of the code listing. The key points are:
a new mapping class called DlsAppOverlordExportRunDataMap
the addition of a UseOverridesFromAssemblyOf clause in
CreateSessionFactory
Also, it turns out that (at least with v. 1.0.0.594) there is a very big gotcha with Automapping - the mapping class (e.g. DlsAppOverlordExportRunDataMap) cannot be in the same Namespace as the domain class (e.g. DlsAppOverlordExportRunData)!
Otherwise, NHibernate will throw "NHibernate.MappingException: (XmlDocument)(2,4): XML validation error: ..." , with absolutely no indication of what or where the real problem is.
This is probably a bug, and may be fixed in later versions of Fluent NHibernate.
namespace DlsAppAutomapped
{
public class DlsAppOverlordExportRunData
{
public virtual int Id { get; set; }
// Note: List<float> needs overrides in order to be mapped by NHibernate.
// See class DlsAppOverlordExportRunDataMap.
public virtual IList<float> RawY { get; set; }
}
}
namespace FrontEnd
{
// NEW - SET UP THE OVERRIDES
// Must be in different namespace from DlsAppOverlordExportRunData!!!
public class DlsAppOverlordExportRunDataMap : IAutoMappingOverride<DlsAppOverlordExportRunData>
{
public void Override(AutoMapping<DlsAppOverlordExportRunData> mapping)
{
// Creates table called "RawY", with primary key
// "DlsAppOverlordExportRunData_Id", and numeric column "Value"
mapping.HasMany(x => x.RawY)
.Element("Value");
}
}
}
private static ISessionFactory CreateSessionFactory()
{
ISessionFactory sessionFactory = null;
const string autoMapExportDir = "AutoMapExport";
if( !Directory.Exists(autoMapExportDir) )
Directory.CreateDirectory(autoMapExportDir);
try
{
var autoPersistenceModel =
AutoMap.AssemblyOf<DlsAppOverlordExportRunData>()
.Where(t => t.Namespace == "DlsAppAutomapped")
// NEW - USE THE OVERRIDES
.UseOverridesFromAssemblyOf<DlsAppOverlordExportRunData>()
.Conventions.Add( DefaultCascade.All() )
;
sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile)
.ShowSql()
)
.Mappings(m => m.AutoMappings.Add(autoPersistenceModel)
.ExportTo(autoMapExportDir)
)
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory()
;
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return sessionFactory;
}
Didn't get any answers here or on the Fluent NHibernate mailing list that actually worked, so here's what I did.
It smells like a horrible hack, but it works. (Whether it will scale up to large data sets remains to be seen).
First, I wrapped a float property (called Value) in a class:
// Hack - need to embed simple types in a class before NHibernate
// will map them
public class MappableFloat
{
public virtual int Id { get; private set; }
public virtual float Value { get; set; }
}
I then declare the properties in other classes that need to be Lists of floats e.g.
public virtual IList<MappableFloat> RawYMappable { get; set; }
NHibernate creates a single database table, with multiple foreign keys, e.g.
create table "MappableFloat" (
Id integer,
Value NUMERIC,
DlsAppOverlordExportRunData_Id INTEGER,
DlsAppOverlordExportData_Id INTEGER,
primary key (Id)
)