NHibernate - Tries to update entity that does not need to be updated - c#

so I have an entity named Event which contains many Reports (one to many) which contains many sources (many to many).
I'm facing a problem that I just cant figure out, when I'm trying to update my event, it will try to update the report because of the cascade (which is fine)
and it will try to update the report's sources on the join table because of the cascade (which is fine), but for some reason it also tries to update the Source entity, which it shouldn't update because there is no change in it.
public class Event
{
public virtual IList<Report> Reports { get; set; }
public virutal int Id { get; set; }
public Event()
{
Reports = new List<Report>();
}
}
public class EventMapping : ClassMap<Event>
{
public EventMapping()
{
Table("EVENTS");
Id(x => x.Id).Column("ID").GeneratedBy.Sequence("EVENT_ID_SEQ");
HasMany(x => x.Reports).KeyCoulmn("EVENT_ID").Cascade.SaveUpdate();
}
}
public class Report
{
public virtual int Id { get; set; }
public virtual int Status { get; set; }
public virtual IList<Source> Sources { get; set; }
public Report()
{
Sources = new List<Source>();
}
}
public class ReportMapping : ClassMap<Report>
{
public ReportMapping()
{
Table("REPORTS");
Id(x => x.Id).Column("ID").GeneratedBy.Sequence("DIVUACH_ID_SEQ");
Map(x => x.Status).Column("Status");
HasManyToMany(x => x.Sources).Table("SOURCES_IN_REPORT").ParentKeyColumn("REPORT_ID").ChildKeyColumn("KOD_SOURCE").Cascade.All();
}
}
public class Source
{
public virtual int Id { get; set; }
}
public class SourceMapping : ClassMap<Source>
{
public SourceMapping()
{
Table("SOURCE");
Id(x => x.Id).Column("ID");
}
}
here is what I do and when it fails.
var eventFromDb = _session.Get<Event>(eventId);
eventFromDb.Reports.ToList().ForEach(x => x.Status = GetStatus());
_session.Update(eventFromDb);
Any idea why?

Unwanted updates usually are cause be accidental changes of properties. NHibernate has automatic change tracking and updates all records when the properties do not return the exact same value that is assigned to it when loading.
See this answers:
Why hibernate always call "update" statement after using "select" statement in MySQL?
Nhibernate doing updates on select?
by the way, you do not need to call update(). NHibernate updates changed entities anyway when they are in the session. Everything you load from the database (e.g. by session.get() in your case) is in the session. Just commit the transaction.

Related

How to avoid child updates inside Fluent NHibernate many-to-many relationship?

I have a simple many-to-many relationship using Fluent NHibernate and it is working pretty fine.
Here is my first class mapping:
public LobMapping()
{
...
HasManyToMany(x => x.Commodities)
.Table("PPBSYS.LOB_COMMODITY")
.ParentKeyColumn("LOB_ID")
.ChildKeyColumn("COMMODITY_ID")
.Cascade.SaveUpdate()
.LazyLoad();
...
}
Here is my second class mapping:
public CommodityMapping()
{
...
HasManyToMany(x => x.Lobs)
.Table("PPBSYS.LOB_COMMODITY")
.ParentKeyColumn("COMMODITY_ID")
.ChildKeyColumn("LOB_ID")
.Inverse()
.Cascade.All()
.LazyLoad();
...
}
And finally, my Lob object has a list of commodities:
public class Lob
{
...
public virtual IEnumerable<Commodity> Commodities { get; set; }
...
}
However I am not happy with the fact that I must reference the entire commodity inside the Lob class. I really would like to do:
var lob = new Lob();
lob.Commodities = new [] { new Commodity { CommodityId = 1 }};
repository.SaveLob(lob);
But if I run the code above, NHibernate will try to update the Commodity table setting the columns to null for the commodity with ID = 1.
So actually I must get the entire object, before saving the Lob:
var lob = new Lob();
lob.Commodities = new [] { repostitory.GetCommodit(1) };
repository.SaveLob(lob);
And I know that the commodity exists, because the user just have selected them.
It is possible to accomplish my task?
I assume your repository is calling session.Get<T>(id) under the covers. There is also session.Load<T>(id).
lob.Commodities = new [] { session.Load<Commodity>(1) };
From the NHibernate documentation on Load()...
If the class is mapped with a proxy, Load() returns an object that is an uninitialized proxy and does not actually hit the database until you invoke a method of the object. This behaviour is very useful if you wish to create an association to an object without actually loading it from the database.
Daniel's answer sounds promising, let me know if in Save the proxy won't hit the database to fill all the properties of the entity.
Another answer would be a little forcefully on Nhibernate, i tested it and it's working,
working code :
public class Com
{
public virtual Guid ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Lob> Lobs { get; set; }
}
public class Lob
{
public virtual Guid ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Com> Coms { get; set; }
}
class LobMap : ClassMap<Lob>
{
public LobMap()
{
Id(x => x.ID).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Coms)
.Table("LOB_COM")
.ParentKeyColumn("LOB_ID")
.ChildKeyColumn("COM_ID")
.Cascade.SaveUpdate()
.LazyLoad();
}
}
class ComMap : ClassMap<Com>
{
public ComMap()
{
Id(x => x.ID).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Lobs)
.Table("LOB_COM")
.ParentKeyColumn("COM_ID")
.ChildKeyColumn("LOB_ID")
.Inverse()
.Cascade.All()
.LazyLoad();
}
}
Now - I set a dummy entity to mimic the connection table and map it :
public class ComLobConnection
{
public virtual Guid ComID { get; set; }
public virtual Guid LobID { get; set; }
}
public class ComLobConnectionMap : ClassMap<ComLobConnection>
{
public ComLobConnectionMap()
{
Table("LOB_COM");
Id();
Map(x => x.ComID,"COM_ID");
Map(x => x.LobID,"LOB_ID");
}
}
Notice that i'm mapping to exact same fields and Table as the ManyToMany, with no ID set up (that's the empty Id() call)
Now all that's left is to save a ComLobConnection and it will be added to the Com.Lobs and Lob.Coms
Saved some test Com,Lob
var testCom = new Com() { Name = "TestCom" };
var testLob = new Lob() { Name = "TestLob" };
session.SaveOrUpdate(testCom);
session.SaveOrUpdate(testLob);
after save - took their ID's and saved a connection to test
var testConnection = new ComLobConnection()
{
ComID = Guid.Parse("D3559F53-8871-45E9-901D-A22800806567"),
LobID = Guid.Parse("D372D430-2E61-44F2-BA89-A228008065F1")
};
session.SaveOrUpdate(testConnection);
This forced a new record in the manytomany table, and worked when getting both Lob and Com after.
I'm not recommending this :), It just interested me if it can be done, and it can.
This way you will not hit the DB for loading Coms or Lobs that you know exist to save their connection.
Hopefully Load() will not hit the DB for that too :)
Edit : can be done with any kind of ID

Fluent NHibernate - Detached table mapping

Is there a way I can setup mapping for a table that doesn't have a direct reference to another table? It actually gets it's reference from another table that I do have a direct reference from.
This is what I have so far, but I'm not sure how to map the "LookupValue" in my MetaData model. It would need to map to MetaData if the [mdd].DefinitionType equals the [mdl].LookupType and the [md].DataValue equals the [mdl].LookupKey.
public class MetaData {
public virtual long TableID { get; set; }
public virtual MetaDataDefinition Definition { get; set; }
public virtual int DefinitionID { get; set; }
public virtual String DataValue { get; set; }
public virtual MetaDataLookup LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataDefinition {
public virtual long ID { get; set; }
public virtual string DefinitionName { get; set; }
public virtual string DefinitionType { get; set; }
}
public class MetaDataLookup {
public virtual string Type { get; set; }
public virtual string LookupKey { get; set; }
public virtual string LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataMap : ClassMap<MetaData> {
public MetaDataMap() {
Table("PPOMetaData");
CompositeId()
.KeyProperty(x => x.TableID, "TableID")
.KeyProperty(x => x.DefinitionID, "DefinitionID");
References(x => x.Defintion, "DefinitionID").Not.LazyLoad().Cascade.All().Fetch.Join();
Map(x => x.TableID);
Map(x => x.DataValue);
}
}
public class MetaDataDefinitionMap : ClassMap<MetaDataDefinition> {
public MetaDataDefinitionMap() {
Table("MetaDataDefinitions");
Id(x => x.ID);
Map(x => x.DefinitionName);
Map(x => x.Type);
}
}
public class MetaDataLookupMap : ClassMap<MetaDataLookup> {
public MetaDataLookupMap() {
CompositeId()
.KeyProperty(x => x.LookupType)
.KeyProperty(x => x.LookupKey);
Map(x => x.LookupValue);
}
}
Ideally, I want to have it run a query similar to this:
SELECT data.TableID, data.DefinitionID, def.DefinitionName, data.DataValue,lu.LookupValue AS DataValue
FROM dbo.PPOMetadata AS data
INNER JOIN dbo.MetaDataDefinitions AS def ON def.ID = data.DefinitionID
LEFT OUTER JOIN dbo.MetaDataLookup AS lu ON lu.LookupType = def.Type AND lu.LookupKey = data.DataValue
WHERE data.TableID = 1
In terms of update ability, the only thing I would ever create, update or delete would be in the MetaData table. The definitions and Lookup values would never change (at least from this part of the application). Is mapping the "MetaDataLookup" directly to the MetaData model possible? If so, can someone point me in the right direction of what I should be looking at?
Thanks!
I came up with a workaround that seems to be working and might take some of the complexity out. Instead of trying to handle the complex joins in a ClassMap, I built a view in Sql Server that does this for me. In my application, I built a new Model and ClassMap for the view. I haven't implemented any update logic yet, but I think I'll have the update logic work directly on the MetaData model, while the read logic (which needs everything joined together) will use the new MetaDataView model.
I'm still curious if complex joins like this are possible in Fluent NHibernate, but for now this solution seems to be working for me.

Fluent NHibernate cascade delete not working

I've got a simple phone directory app using Fluent NHibernate 1.1. In the app, a "Person" object has many "PhoneNumber" objects. I'm trying to delete a Person and I want to cascade deletes to PhoneNumbers. I set a convention of DefaultCascade.All() after reading this answer. However, attempting to delete the parent object still throws an exception--it appears that NHibernate is trying to update the child table to set the parent ID to null instead of just deleting the record:
{"could not delete collection: [Person.PhoneNumbers#473][SQL: UPDATE phone_numbers SET person_id = null WHERE person_id = #p0]"}
InnerException:
{"Cannot insert the value NULL into column 'person_id', table 'directory.dbo.phone_numbers'; column does not allow nulls. UPDATE fails.\r\nThe statement has been terminated."}
My Fluent config is:
public static ISessionFactory CreateSessionFactory() {
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["activeConnStr"]].ConnectionString))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Person>()
.Conventions.Add(DefaultCascade.All())
)
.BuildSessionFactory();
}
The parent class is:
public class Person {
public Person() {
PhoneNumbers = new List<PhoneNumber>();
EmailAddresses = new List<string>();
}
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Company { get; set; }
public virtual IList<PhoneNumber> PhoneNumbers { get; set; }
public virtual IList<string> EmailAddresses { get; set; }
}
The child class (PhoneNumber) is:
public class PhoneNumber {
public virtual string Number { get; set; }
public virtual PhoneNumberType NumberType { get; set; }
public virtual Person Person { get; set; }
}
My code to delete a person is:
public static void DeletePerson(int id) {
using (var session = Dalc.Instance.SessionFactory.OpenSession()) {
using (var trans = session.BeginTransaction()) {
session.Delete(session.Load<Person>(id));
trans.Commit();
}
}
}
What am I doing wrong?
I'm not sure about configuring the Fluent part, but I recently had the same problem with ActiveRecord.
You need to set your association, on the Person side, as Inverse = true.
From looking at the Getting Started documentation...
I belive, you need to set this when defining your HasMany relationship in Person. It should look something like this:
public PersonMap()
{
//<...SNIP...>
HasMany(x => x.PhoneNumbers)
.Inverse();
//<...SNIP...>
}
It works; Here is what each cascade option means:
none - do not do any cascades, let the users handles them by themselves.
save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
delete - when the object is deleted, delete all the objects in the association.
delete-orphan - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
all-delete-orphan - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id);
Map(x => x.Name);
HasMany<PhoneNumber>(x => x.PhoneNumberList)
.KeyColumn("PersonId")
.Cascade.All()
.Inverse().LazyLoad();
}
}

Problem Deleting with Parent/Child

I have an issue when deleting a child record from the session. Here are the entities i have defined:
public class ReportedData
{
public virtual int ReportedDataID { get; set; }
public virtual ReportedDataTypes Type { get; set; }
public virtual string Reason { get; set; }
public virtual IList<ArticleCommentReported> ArticleCommentsReported { get; private set; }
public virtual IList<ForumPostReported> ForumPostsReported { get; private set; }
public ReportedData()
{
ArticleCommentsReported = new List<ArticleCommentReported>();
ForumPostsReported = new List<ForumPostReported>();
}
}
public class ArticleCommentReported : ReportedData
{
public virtual ArticleComment Comment { get; set; }
}
public class ForumPostReported : ReportedData
{
public virtual ForumPost Post { get; set; }
}
With the following fluent mapping:
public ReportedDataMap()
{
Table("ReportedData");
Id(x => x.ReportedDataID);
Map(x => x.Type, "TypeID");
Map(x => x.Reason);
HasMany(x => x.ArticleCommentsReported)
.KeyColumn("ReportedDataID")
.Inverse()
.Cascade.All();
HasMany(x => x.ForumPostsReported)
.KeyColumn("ReportedDataID")
.Inverse()
.Cascade.All();
}
public class ArticleCommentReportedMap : SubclassMap<ArticleCommentReported>
{
public ArticleCommentReportedMap()
{
Table("ArticleCommentsReported");
KeyColumn("ReportedDataID");
References(x => x.Comment, "CommentID");
}
}
public class ForumPostReportedMap : SubclassMap<ForumPostReported>
{
public ForumPostReportedMap()
{
Table("ForumPostsReported");
KeyColumn("ReportedDataID");
References(x => x.Post, "PostID");
}
}
Now say i try the following (i have added comments to help you understand what's going on):
// Loop over the reported data (this is my view model and not my actual model which contains an extra property for the action they wish to carry out)
foreach (var reportedData in model)
{
// If the action is leave then do nothing (else we always delete the reported data)
if (reportedData.Action != ReportedDataActions.Leave)
{
// Switch over the type since we need to make sure it deletes the article comment or post if the action is set to delete
switch (reportedData.Type)
{
case ReportedDataTypes.ArticleComment:
var reportedComment = _context.Repository<ArticleCommentReported>().GetByID(reportedData.ReportedDataID);
if (reportedData.Action == ReportedDataActions.Delete)
_context.Repository<ArticleComment>().Delete(reportedComment.Comment);
_context.Repository<ArticleCommentReported>().Delete(reportedComment);
break;
case ReportedDataTypes.ForumPost:
var reportedPost = _context.Repository<ForumPostReported>().GetByID(reportedData.ReportedDataID);
if (reportedData.Action == ReportedDataActions.Delete)
_forumService.DeletePost(reportedPost.Post);
_context.Repository<ForumPostReported>().Delete(reportedPost);
break;
}
}
}
_context.Commit();
When the user trys to delete a forum post (the Action against the reported data is set to delete) it throws the following error:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [ForumPostReported#2]
I could probably set some mapping up to automatically delete the post/comment once the reported data is deleted but i only want to delete the post/comment if the action is set to delete.
I'd appreciate it if someone could help. Thanks
Problem solved! I just had to delete the reported item first. Seems kinda backwards but it works and that's all i care.

Getting Data from all tables instead of one with HQL Query that should only get 1 table's data

I have created 3 tables in my database and put data into them. The 3 tables all have foreign keys joining them together. Below are the table classes and there mappings. When I run the query listed at the end I get IList<> of the objects and they have the data from all 3 tables. However, my HQL query is only from the top most table. How can I get back just the results from the top most table?
These are my classes:
public class Technology
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual int SortOrder { get; set; }
public virtual string Abbreviation { get; set; }
public virtual IList<TechnologyDescription> TechnologyDescriptions { get; private set; }
public Technology()
{
TechnologyDescriptions = new List<TechnologyDescription>();
}
public virtual void AddTechnologyDescription(TechnologyDescription technologyDescription)
{
technologyDescription.Technology = this;
TechnologyDescriptions.Add(technologyDescription);
}
}
public class TechnologyDescription
{
public virtual int Id { get; private set; }
public virtual Technology Technology { get; set; }
public virtual string Description { get; set; }
public virtual DescriptionType DescriptionType { get; set; }
}
public class DescriptionType
{
public virtual int Id {get; private set;}
public virtual string Type { get; set; }
}
These are my mapping objects:
public class TechnologyMap : ClassMap<Technology>
{
public TechnologyMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.SortOrder);
Map(x => x.Abbreviation);
HasMany(x => x.TechnologyDescriptions)
.Inverse()
.Cascade.All();
}
}
public class TechnologyDescriptionMap : ClassMap<TechnologyDescription>
{
public TechnologyDescriptionMap()
{
Id(x => x.Id);
References(x => x.Technology);
Map(x => x.Description);
References(x => x.DescriptionType);
}
}
public class DescriptionTypeMap : ClassMap<DescriptionType>
{
public DescriptionTypeMap()
{
Id(x => x.Id);
Map(x => x.Type);
}
}
And this is my HQL code:
IQuery q = session.CreateQuery("from Technology T");
IList technologies = q.List();
I don't know if it is possible using HQL, but using NHibernate's Criteria API, you can do this:
ICriteria criteria = session.CreateCriteria (typeof(Technology));
criteria.SetFetchMode ("TechnologyDescriptions", FetchMode.Lazy);
var list = criteria.List<Technology>();
However, this is probably not really what you want. The TechnologyDescriptions won't be fetched right now, but they will be fetched once you access them (that is: the first time you call the TechnologyDescriptions property).
When working with NHibernate, you shouldn't think in terms of 'data'. Rather, you should think in terms of 'entities'.
When retrieving an entity, you want to retrieve the entity entirly (directly, or in a lazy fashion). It is not possible to retrieve an entity partially, and this is quite obvious;
What should NHibernate do with an entity that you've retrieved partially, when you try to save that entity ?
Something else that pops in my mind:
I suppose you want to retrieve the Technologies, and nothing related because you want to display them in an overview or something like that ?
In such case, you should take a look at 'Transformations'.
You could for instance create an additional class which is called TechnologyView, which looks like this:
public class TechnologyView
{
public int Id
{
get;
private set;
}
public string Name
{
get;
private set;
}
public string Abbreviation
{
get;
private set;
}
private TechnologyView()
{
// Private constructor, required for NH
}
public TechnologyView( int id, string name, string abbreviation )
{
this.Id = id;
this.Name = name;
this.Abbreviation = abbreviation;
}
}
Once you've done this, you must inform NHibernate about the existance of this class.
You do this by Importing the class in an hbm.xml file for instance . (I do not know how to do it using Fluent).
<import class="MyNamespace.TechnologyView" />
After that, you can create a query (using HQL or Criteria) which retrieves TechnologyView instances. NHibernate is smart enough to generate a performant SQL query.
using HQL:
IQuery q = s.CreateQuery ("select new TechnologyView (t.Id, t.Name, t.Abbreviation) from Technology t");
using Criteria:
ICriteria criteria = s.CreateCriteria (typeof(Technology));
criteria.SetResultTransformer (Transformers.AliasToBean (typeof(TechnologyView));
var result = criteria.List<TechnologyView>();
I think what you're looking for is for the TechnologyDescriptions to be lazy loaded. That way the descriptions get loaded from the database only when they are accessed (NHibernate will issue a second db query. Note that this can lead to N+1 selects in some situations and you might prefer the all at once query depending on the usage.)
By NHibernate xml mappings default to lazy loading of collections. In the past it seems that the Fluent NHibernate did not have the same default. You need to add .LazyLoad() to the mapping.
Recently it looks like lazy loading has become the default fluent mapping:
Is the default behavior with Fluent NHibernate to lazy load HasMany<T> collections?

Categories

Resources