Im using the Fluent NHibernate together with the automapping functionality. Now im lookin' for something like a configuration, setting, custom attribute - whatever - to declare an entity property as "ReadOnlyFromDb"
In the MsSql database im using a computed column in one of my tables where a value is calculated depending on some other values of the specific data row. Now I have declared this column in the entity class as
public virtual int STATUS { get; private set; }
On getting the specific data of the table everything works fine. The property STATUS is filled correct with the specific value in the datatable.
The problem occures when i try to SaveUpdate() the specific object.
I always get the exception
A computed column cannot be the target of an INSERT or UPDATE statement
Which is correct for my understanding - and its how it was supposed to be ;)!
Basically im looking for a configuration, setting, custom attribute - whatever - to say
Hey Fluent NHibernate - get the specific property propertyName but do not insert / update the property propertyName
Is there something like that? Or does a workaround exists for this case?
I've searched the fluent nhibernate wiki but have not found a smilliar case.
I hope that someone has already faced and solved this problem!
Here is the code snippet how I create the session (maybe it helps):
public static ISessionFactory GetNHibernateSession()
{
if (nhibernateSession != null)
return nhibernateSession;
if (ConfigurationManager.AppSettings[msSqlConnectionString] == null || ConfigurationManager.AppSettings[msSqlConnectionString] == String.Empty)
throw new NullReferenceException(String.Format("AppSetting '{0}' must not be null!", msSqlConnectionString));
//nhibernateSession = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConfigurationManager.AppSettings[msSqlConnectionString]))
// .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<T_ABWEICHUNGEN>().IgnoreBase<BaseClass>)).BuildSessionFactory();
nhibernateSession = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConfigurationManager.AppSettings[msSqlConnectionString]))
.Mappings(m => m.AutoMappings.Add(AutoMap.Assembly(System.Reflection.Assembly.GetExecutingAssembly())
.Override<T_ABWEICHUNGEN>(map =>map.Map(d => d.A08_STATUS_FILE).Length(2147483647))
.IgnoreBase(typeof(BaseClass))
.IgnoreBase(typeof(IDColumn))
.IgnoreBase(typeof(MsSqlQuery))
.IgnoreBase(typeof(MsSqlParamCollection))
.IgnoreBase(typeof(AbweichungStatus))
)).BuildSessionFactory();
return nhibernateSession;
}
}
thanks so far for the response - helped me so far.
But - is there a way to get this solved more 'dynamicly'?
For example:
I want to declare a custom attribute called [ReadOnlyDbField] now declare all properties of the entity with this cusomt attribute to say: Just read this value and do not update / insert it.
Basically i want to say in the configuration
Map all properties with the custom attribute [ReadOnlyDbField] to Not.Insert().Not.Update()
Is there a way to implement this?
.Override<Entity>(map => map.Map(d => d.STATUS).Not.Insert().Not.Update())
update: account for edited question
public class ReadonlyDbFielConvention : AttributePropertyConvention<ReadOnlyDbField>
{
protected override void Apply(ReadOnlyDbField attribute, IPropertyInstance instance)
{
instance.Not.Insert();
instance.Not.Update();
}
}
Related
ASP.NET Core 2 Web application using a REST API. Currently using sqlite3 for development database. (Also tried migrating to SQL Server and got same results as below).
I'm sending an entity to web client, the client makes changes to the entity that involve adding a new related entity and then that updated principle entity gets sent back as json in body of PUT a request.
I was hoping the new related entity would get created automatically, but this is not happening. The simple properties on the principle entity are updated properly, but not reference properties. I'm not getting any exceptions or anything - it just seems to be ignoring the reference properties.
Simplified Classes (I removed other properties that shouldn't affect the relationship):
public partial class DashboardItem {
public int Id { get; set; }
public int? DataObjectId { get; set; }
public DataObject DataObject { get; set; }
}
public partial class DataObject {
public int Id { get; set; }
}
Portion of DbContext Fluent API for associated property:
modelBuilder.Entity<DashboardItem>(entity => {
entity.HasOne(p => p.DataObject)
.WithMany()
.HasForeignKey(p => p.DataObjectId);
});
Controller Method for PUT:
[HttpPut("{id}")]
public async Task<IActionResult> PutDashboardItem([FromRoute] int id, [FromBody] DashboardItem entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != entity.Id)
{
return BadRequest();
}
_context.Entry(entity).State = EntityState.Modified;
try{
await _context.SaveChangesAsync();
}catch (DbUpdateConcurrencyException)
{
if (!DashboardItemExists(id)){
return NotFound();
}else {
throw;
}
}
return NoContent();
}
The simplified json (without all the other properties) would look like this (I've tried different variations of have the foreign key "DataObjectId" removed from the json, set to null, or set to zero in case that might be interferring.):
{
Id:1,
DataObjectId:null,
DataObject:{
Id: 0
}
}
When debugging in the controller action method, the existing "DashboardItem" principle entity created from the request body has the reference property "DataObject" populated before getting added to the DbContext, but the new DataObject never gets created in the database. There is only a SQL UPDATE statement issued for DashboardItem and no INSERT for DataObject.
I've also tried making the controller method synchronous instead of async, using DbContext.SaveChanges() instead of .SaveChangesAsync(), since there used to be a problem with that in earlier versions of EF Core related to creating related entities, even though I'm using 2.0 which already has a fix for that. Same result.
This EFCore Doc sounds like it should just work out of the box.
This has worked for me in a prior project. What am I missing here?
Basically, my mistake was in assuming the process of updating data was much simpler than it actually is when sending the updated data from a client in a web application.
After digging a lot more, it seems that the following line in my controller method for handling the PUT request is the problem:
_context.Entry(entity).State = EntityState.Modified;
Setting the entity entry state to Modified in this way results in Entity Framework Core ignoring the reference properties for the related objects - the SQL UPDATE generated will only address the columns in the entity table.
This simple summary eventually got me started down the right path.
Summarizing what I've now learned:
This controller method is dealing with a 'detached' entity that was edited and sent back from the client. The DbContext is not yet tracking this entity since I get a new instance of the context with each http request (hence the entity is considered 'detached'). Because it is not being tracked yet, when it is added to the DbContext, the context needs to be told whether this entity has been changed and how to treat it.
There are several ways to tell the DbContext how to handle the detached entity. Among those:
(1) setting the entity state to EntityState.Modified will result in ALL properties being included in the SQL update (whether they've actually changed or not), EXCEPT for the reference properties for related entities:
_context.Entry(entity).State = EntityState.Modified;
(2) adding the entity with a call to DbContext.Update will do the same as above, but will include the reference properties, also include ALL properties on those entities in the update, whether they've changed or not:
_context.Update(entity)
Approach #2 got things working for me, where I was just trying to get the new related child entity to be created in the Update to its parent.
Beyond that, DbContext.Attach() and DbContext.TrackGraph sound like thy provide more find-grained control over specifying what specific properties or related entities to include in the update.
I'm using mapping by code in NHibernate.
I got a class with several properties. One of them is not related to any columns in DB but still has getter and setter.
I use ConventionModelMapper not ModelMapper. The first one assumes that all properties are mapped.
How i can tell to NHibernate to ignore it?
I find it easier to just create an attribute, attach that attribute to the property, and check for it in the mapper.IsPersistentProperty method. Something like this:
class IngnoreAttribute : Attribute
{
}
class Foo
{
[Ignore]
public virtual string Bar { get; set; }
}
mapper.IsPersistentProperty((mi, declared) => mi.GetCustomAttribute<IgnoreAttribute>() == null);
This way, I don't have to keep a list of properties to be ignored at the mapping codes.
Why not map the properties you want and leave the ones not needed to be mapped
check this
You can manage the persistence of ConventionModelMapper as following:
mapper.BeforeMapProperty += (mi, propertyPath, map) =>
{
// Your code here using mi, propertyPath, and map to decide if you want to skip the property .. can check for property name and entity name if you want to ignore it
};
A better answer would be:
mapper.IsPersistentProperty((mi, declared) =>
{
if (mi.DeclaringType == typeof (YourType) && mi.Name == "PropertyNameToIgnore")
return false;
return true;
});
If you do not mention the property that should be ignored in your NHibernate mapping, NHibernate will ignore it.
I have a class which I would like to map as a component onto any table which contains it:
public class Time
{
public int Hours { get; set; }
public int Minutes { get; set; }
public int Seconds { get; set; }
}
I would like to store this class as a bigint in the database - the same as how TimeSpan is stored but my class has completely different behaviour so I decided to create my own.
I'm using FLH's automapper and have this class set as a component (other classes have Time as a property). I've got as far as creating an override but am not sure how to go about mapping it:
I gave it a try this way:
public class TimeMappingOverride : IAutoMappingOverride<Time>
{
public void Override(AutoMapping<Time> mapping)
{
mapping.Map(x => x.ToTimeSpan());
mapping.IgnoreProperty(x => x.Hours);
mapping.IgnoreProperty(x => x.Minutes);
mapping.IgnoreProperty(x => x.Seconds);
}
}
But got this error:
Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MethodCallExpression'.
How should I go about this?
Details of components can be found here: http://wiki.fluentnhibernate.org/Fluent_mapping#Components
But first of all, you can't map a method.
Assuming you change ToTimeSpan() to a property AsTimeSpan, there are two ways to do it, only the harder of which will work for you because you are using automapping:
Create a ComponentMap<Time> -- once done, your existing mapping will just work. This is not compatible with automapping.
Declare the component mapping inline:
mapping.Component(x => x.AsTimeSpan, component => {
component.Map(Hours);
component.Map(Minutes);
component.Map(Seconds);
});
You'll have to do this every time, though.
Of course, this doesn't address "I would like to store this class as bigint…"
Are you saying you want to persist it as seconds only? If so, scratch everything at the top and again you have two options:
Implement NHibernate IUserType (ugh)
Create a private property or field that stores the value as seconds only, and wire only this up to NHibernate. The getters and setters of the pubic properties will have to convert to/from seconds.
I personally haven't worked with AutoMappings yet, but my suggestion would be to look into NHibernate's IUserType to change how a type is being persisted. I believe that's a cleaner way of defining your custom mapping of Time <-> bigint.
Reading the code above, Map(x => x.ToTimeSpan()) will not work as you cannot embed application-to-database transformation code into your mappings. Even if that would be possible, the declaration misses the transformation from the database to the application. A IUserType, on the other hand, can do custom transformations in the NullSafeGet and NullSafeSet methods.
when i use saveOrUpdate(Object) method of Hibernate. How can I know that row is updated or new row added into table??? Return type of method saveOrUpdate(Object) is void, so am not able to find out the result after calling this method.
kindly help me.
As I understand from your question and comment lived. You could create an event listener and implement two Interfaces: IPreUpdateEventListener, IPreInsertEventListener
e.q.
public class AuditEventListener : IPreUpdateEventListener, IPreInsertEventListener
{
public bool OnPreUpdate(PreUpdateEvent #event)
{
//your stuff here
return false;
}
public bool OnPreInsert(PreInsertEvent #event)
{
//your stuff here
return false;
}
}
but I think this is ridiculous. using ORMs means that you do not care about persistence and all work is done the unitofwork. If you really need to insert and update just use Save() or Update() methods, in this way you will exactly know what operation is made.
If your object to be persisted is having identifier property set to 0(/id null) then it means it is a new object and will be inserted newly in db.
After it is inserted hibernate will then set the id value in identifier field.
If already the object has identifier property set then it means the object is already persisted and it can be updated
EDIT:Did you look at hibernate interceptors?May be this is useful.
example
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)
)