Blockquote
We are using the ByCode method of mapping our data..
I have Process (Process table) object which has a List of ProcessStep (ProcessStep table) objects which in turn has a Bag of ProcessStepUser (ProcessStepUser table) objects assigned to each step.
The objects load just fine, but in the web page if I delete a Step from the collection, rehydrate into MVC action as a Process and then save the process, the Step in the database simply has the ProcessId set to null which breaks the link, but leaves the Step in the Database and all its assigned users.
What I want to be able to do is delete a Step and have its ProcessStep and all its ProcessStepUsers deleted.
Likewise, when I edit a ProcessStep (say I change the name of the step) and save, it's saving that, but the ProcessStep users are nulled out (of their ProcessStepId) and recreated, leaving orphaned records.
I do have a column on ProcessStepUser for overall ProcessId as well, so that I can prevent a User from being assigned more than once to any step of the process.
My relevant mapping is as follows:
Process:
List(x => x.ProcessSteps, m =>
{
m.Key(k => k.Column("ProcessId"));
m.Index(i => i.Column("StepOrder"));
m.Lazy(CollectionLazy.NoLazy);
m.Cascade(Cascade.All);
m.Inverse(false);
},
r => r.OneToMany(o => o.Class(typeof (ProcessStep))));
ProcessStep:
Bag(x => x.ProcessStepUsers, m =>
{
m.Key(k => k.Column("ProcessStepId"));
m.Lazy(CollectionLazy.NoLazy);
m.Cascade(Cascade.All);
m.Inverse(false);
},
r => r.OneToMany(o => o.Class(typeof (ProcessStepUser))));
ManyToOne(x => x.Process, m =>
{
m.Column("ProcessId");
m.Class(typeof (Process));
});
ProcessStepUser
ManyToOne(p => p.Step, m =>
{
m.Column("ProcessStepId");
m.Class(typeof (ProcessStep));
});
ManyToOne(p => p.Process, m =>
{
m.Column("ProcessId");
m.Class(typeof (Process));
});
As I said, it saves just fine on creation and loads just fine for display. But the dropping of a step or editing of a step is creating havoc in the Database.
I hacked the step deletion process by flagging them and manually deleting them before saving the Process master object, but I'd like nhibernate to do it all if possible.
Thanks!
Blockquote
First off thanks to Martin who caught the one issue with the Cascade.All.Include(Cascade.DeleteOrphans)... that was the original code I had, I forgot I'd changed it while attempting to solve the problem.
I rewrote some tests and the mapping actually is working just fine, so apologies there. I would delete this question, but the site is not letting me to anything (in Firefox) but edit...
The problem ended up being because I was getting the Process object from the database and then serializing it up to the View as a JSON object. I then passed back the JSON object which was bound back to the Process object.
The long and short is that our repository was calling SaveOrUpdate(obj) on the object, but since it had been disconnected, we needed to call Merge(obj) which we did and worked perfectly.
I leave it to the powers that be as to whether this question has any value remaining.
Thanks!
Try Cascade(Cascade.AllDeleteOrphan) on your associations where you want them to be deleted instead of having the other side nulled...
Related
here is the code that runs and that results in two sentmessage objects getting added to the table:
$sentmessage = null;
//add entry into sent message table Tx_BpsMessagecentre_Domain_Model_Sentmessage
$sentmessage = $this->objectManager->create('Tx_BpsMessagecentre_Domain_Model_Sentmessage');
//now just fill in the object
$sentmessage->setBpsmessageid($bPSMessage->getUid());
$sentmessage->setBody($bPSMessage->getBody());
$sentmessage->setSubject($bPSMessage->getSubject());
$sentmessage->setCouponlist($bPSMessage->getCouponlist());
$sentmessage->setHallname($bPSMessage->getHall()->getHall());
$sentmessage->setHalladdress($bPSMessage->getHall()->getAddress());
$sentmessage->setHallurl($bPSMessage->getHall()->getUrl());
$sentmessage->setBanner($bPSMessage->getBanner());
$this->sentmessageRepository->add($sentmessage);
$this->objectManager->get('Tx_Extbase_Persistence_Manager')->persistAll();
die; //if I take out this die and the persist call above I still get two records added
I am using typo3 v 4.5.32 with extbase 1.3.
The sentmessage object is one I had to create manually - ie without extension builder, so there might be some misconfiguration in the TCA somewhere but I have no idea what would cause this.
Thanks
PS: a piece of my ext_localconf.php showing some of my plugins
Tx_Extbase_Utility_Extension::configurePlugin(
$_EXTKEY,
'Bpsmonthly',
array(
'BPSMessage' => 'cronMonthlys'
),
// non-cacheable actions
array(
'BPSMessage' => 'create, update, delete',
)
);
Tx_Extbase_Utility_Extension::configurePlugin(
$_EXTKEY,
'Bpsbirthdays',
array(
'BPSMessage' => 'cronBirthdays'
),
// non-cacheable actions
array(
'BPSMessage' => 'create, update, delete',
)
);
Tx_Extbase_Utility_Extension::configurePlugin(
$_EXTKEY,
'BpsAnnuals',
array(
'BPSMessage' => 'cronAnnuals'
),
// non-cacheable actions
array(
'BPSMessage' => 'create, update, delete',
)
);
Ok, found the bug, it was actually caused by my debug code that spit some url out to the page, the url got resolved by the browser to hit the action again (probably a url in an img tag) and boom. so the bug only exists while debugging. I love programming.
Is there a way to delete my dumbest questions from stackoverflow?
most probably you have the two plugins from the same extension in your website - therefore everything is processed twice.
Maybe persisting your changes takes a loooooong time (e.g., >30 sec). In such cases, I experienced that under some conditions the browser posts again (!) the query, resulting in what you describe.
As I experienced the same behaviour, but under other circumstances, I'm adding my case as an answer here, maybe it helps someone.
If you're working with multiple records in the same form (model having child records) and they all are not persisted, make sure to not set the parent record on the child records.
This will lead to the multiple-rows behaviour:
$foo = ObjectManager->get('Foo\\Bar\\Domain\\Model\\Foo');
$bar = ObjectManager->get('Foo\\Bar\\Domain\\Model\\Bar');
$bar->setFoo($foo); // <-- Should not be done
$foo->add($bar);
With RavenDB, is it possible to get the IDs of a property within another property? For example, if Foo has a list of Bar objects, and each Bar object has a SnuhId property, can I use an Include that gets the IDs of each Snuh property?
I tried the query below, but I get a RavenDB exception: index out of range. In this query, ApplicationServer is a root element, and it has a list of ApplicationsWithOverrideGroup objects. Each of those objects has an ApplicationId property. It's the ApplicationId that I want to get in the include.
IEnumerable<ApplicationServer> appServers = QueryAndCacheEtags(session =>
session.Advanced.LuceneQuery<ApplicationServer>()
.Include(x => x.CustomVariableGroupIds)
// This is the line I'm trying to make work:
.Include(x => (from item in x.ApplicationsWithOverrideGroup select item.ApplicationId).ToList())
).Cast<ApplicationServer>();
Either of these approaches appears to be working. Need to thoroughly test.
.Include(x => x.ApplicationsWithOverrideGroup)
or
.Include(x => x.ApplicationsWithOverrideGroup[0].ApplicationId)
If that first option is indeed working, then a property, specified in an Include(), will include the ID properties within it. Is that right?
I'm not sure if both of those are really working, but they seem to be. If they both work, I wonder if one is better than the other...
Ok, that's NOT WORKING. The NumberOfRequests is increasing, which I'm guessing means the number of trips to the DB is increasing, instead of just what's in the session.
Ok, none of those suggestions worked in the question above. I think what has to be done is to include the IDs of the nested properties in the root object.
And that's what I did, and I was able to get the NumberOfRequests on the session object down to one. Curiously, I had to change this:
// Not using this, at least for now, because it increased the NumberOfRequests on the session...
appServer.CustomVariableGroups = new ObservableCollection<CustomVariableGroup>(
QueryAndCacheEtags(session => session.Load<CustomVariableGroup>(appServer.CustomVariableGroupIds)).Cast<CustomVariableGroup>());
To this:
// ... however, this kept the NumberOfRequests to just one. Not sure why the difference.
appServer.CustomVariableGroups = new ObservableCollection<CustomVariableGroup>();
foreach (string groupId in appServer.CustomVariableGroupIds)
{
appServer.CustomVariableGroups.Add(QuerySingleResultAndCacheEtag(session => session.Load<CustomVariableGroup>(groupId)) as CustomVariableGroup);
}
I am building a small web application and using nhibernate as my DAL.
I have the following situation:
Father class - Profile Class:
{
long ID
string Name
[A whole bunch of info]
List<Voting> Votes
}
Son class - Votes Class:
{
long id
short rating
}
This is my mapping:
HasMany(o => o.Votes)
.LazyLoad()
.AsBag()
.Inverse()
.Cascade.All();
I want the following to happen:
Users will be able to rate a profile, and then all I need to do is merely call update on the profile itself, instead of sending a Save call on each Vote.
This i what I am currently doing:
var myProfile = new Profile();
myProfile.Rate(1,2);
myProfile.Rate(5,2);
myProfile.Rate(20,2);
Session.SaveOrUpdate(myProfile);
Sadly enough nothing gets saved. Why?
Thank you!
If your one-to-many is mapped as inverse, the other side needs to know the parent object. So your Votes class needs a reference to a Profile (mapped as many-to-one).
You also have to make sure that this reference to the parent object is set in your code.
Additional note: Your code sample doesn't make much sense. If you create a new profile, you have to call Session.Save and not Session.Update.
I just started with NHibernate, created my mappings using fluent NHibernate as follows:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x._id, "Id");
Map(x => x._KdNr, "KdNr");
Map(x => x._Name, "Name");
HasMany(x => x._Contact)
.Table("Contacts")
.KeyColumn("FKCustomerID")
.LazyLoad();
}
}
public class ContactMap : ClassMap<Contact>
{
public ContactMap()
{
Id(x => x._id, "Id");
Map(x => x._Name, "Name");
}
}
And to save new records works also:
public static void AddCustomer(Customer cust)
{
using (var session = SessionFactory.Instance.OpenSession())
{
session.Save(cust);
session.Save(cust._Contact);
session.Flush();
}
}
Then I tried to select the customer I added using:
using (var session = SessionFactory.Instance.OpenSession())
{
try
{
var v = session.CreateQuery("from Customer").List<Customers>();
return (List<Customer>)v;
}
catch
{
session.Close();
throw;
}
finally
{
session.Disconnect();
}
}
}
The Customer also is loaded fine, but the contacts inside are not referenced with the following error:
Initializing[fnh.DataModel.Customer#d2f2d1c5-7d9e-4f77-8b4f-9e200088187b]-failed
to lazily initialize a collection of role:
fnh.DataModel.Kunde._Contact, no session or session was closed
But I cannot understand where the error comes from because the session is closed after executing my HQL error...
The problem is that the contacts are lazy loaded, ie. the collection is not fetched from the database in the initial query. I presume that you are passing the objects directly to the view rather than using a viewmodel? You have a few options, each have their drawbacks.
Keep the session open while the view
is being process (so called Open
Session In View approach). You are probably closing the NH session in the controler at the moment right?
Eager load the whole object graph
using .Not.Lazyload() on the
contacts. (Not recommended)
Copy all the data you need to a view
model in the controller, and pass
this to the view. Use automapper to help you with this.
Update in response to comment:
There is still great benefit in leaving collections lazy loaded. Sure, in this context you don't benefit from lazy loaded contacts on the customer object, becase you need to use them. But in another context you might only need the customer name and Id - and you can rest assured that this wont generate a big query with joins etc.
To utilize Open Session in View you don't have to pass your session to the view explicitly, rather you just pass the object as before, but leave the session open - NH will automatically generate a new query when you try to access the contacts collection. (This works becuase the customer object is still 'attached' to an open session behind the scenes). The only difference here is that you need to close the session not in the controller (where it is currently being closed explicitly with the .close() or implicitly with 'using'). Regarding where to open and close the session - there are different approaches:
In the global.asax - open in Application_BeginRequest and close in Application_EndRequest (see: this article) I'd recomend starting with this for the sake of simplicity if you want to do Open Session in View.
In a http module (basically the same as the last, but modularised) (see this article)
With these last two, you are probably thinking 'But that will mean creating a session for every page request!' - and you would be right, but really how many pages are not going to go to the DB? plus session creation is lightweight once the session factory is created.
Using an Attribute decorating the action method (you basically do the same thing as the last two, but this way you can choose which actions open an NH session). Close the session in OnActionExecuted if you want it closed after the action has completed. Close in OnResultExecuted if you want open session in view.
Of course, many people don't like Open Session in View, becuase you cannot control what queries are being generated purely in the controller - the view is triggering many queries, this can cause unpredictable performance.
3 is subtely different from 2, because in situations where you don't need the lazy loaded collection it is not fetched from the DB. For instance, in this case you might be copying to a ViewModel with the full object graph, but in another situation you might use a ViewModel with just the customer name and Id - in this case the joins for the contacts collection would not needlessly be executed. In my opinion 3 is the proper way to do things - but you end up creating many more objects (becuase of the view objects).
AutoMapper is great, saves a lot of time, but when I started looking at the performance of my application AutoMapper is responsible for performance loss.
I'm using lazy loading with NHibernate. Most of the time a need parent entity without needing to access child entities at all. In reality what happens is that AutoMapper tries to map as many relationships as possible causing NHibernate to lazy load all the child entities (I'm seeing SELECT N+1 happening all the time).
Is there way to limit how deep AutoMapper goes or is it possible for AutoMapper to map child entities lazily?
You could use the ignore method for associations you don't need to have loaded.
Mapper.CreateMap<User, UserDto>()
.ForMember(dest => dest.LazyCollection, opt => opt.Ignore())
.ForMember(dest => dest.AnotherLazyCollection, opt => opt.Ignore())
Mapper.CreateMap<UserProperty, UserPropertyDto>()
.ForMember(dest => dest.PropertyLazyReference, opt => opt.Ignore());
return Mapper.Map<User, UserDto>(user);
For associations you know you will need in your dto, you should look at ways of fetching these more efficiently with the initial query, but that is a whole new problem.
Perhaps you should consider using two different dtos; one that includes the child entities, and one that doesn't. You can then return the proper dto from your service layer depending upon the context.
I'm using pre conditions to prevent data from being mapped.
CreateMap<Team, TeamDto>()
.ForMember(dto => dto.Users, options =>
{
options.PreCondition(ctx => !ctx.Items.ContainsKey(AutoMapperItemKeys.SKIP_TEAM_USERS));
options.MapFrom(t => t.TeamUsers.Where(tu => tu.IsDeleted == false));
})
.ReverseMap();
When Map() is called I feed the Items dictionary with skip keys for the properties I don't want mapped.
this.mapper.Map<IEnumerable<Team>, IEnumerable<TeamDto>>(teams, opts =>
{
opts.Items.Add(AutoMapperItemKeys.SKIP_TEAM_USERS, true);
});
Advantages:
you can fine-grain which properties not to map
prevents from mapping to deep with nested objects
no need for duplicate dto's
no duplicate mapping profiles