OptaPlanner - The entity was never added to this ScoreDirector error - optaplanner

I am implementing an algorithm similar to the NurseRoster one in OptaPlanner. I need to implement a rule in drools that check if the Employee cannot work more days than the number of days in his contract. Since i couldn't figure out how to make this in drools, i decided to write it as a method in a class, and then use it in drools to check if the constraint has been broken. Since i needed a List of ShiftAssignments in the Employee class, i needed to use an #InverseRelationShadowVariable that updated that list automatically an Employee got assigned to a Shift. Since my Employee now has to be a PlanningEntity, the error The entity was never added to this ScoreDirector appeared. I believe the error is caused by my ShiftAssignment entity, which has a #ValueRangeProvider of employees that can work in that Shift. I think this is due to the fact that ScoreDirector.beforeEntityAdded and ScoreDirector.afterEntityAdded were never called, hence the error. For some reason when i removed that range provider from ShiftAssignment and put it on NurseRoster which is the #PlanningSolution, it worked.
Here is the code:
Employee:
#InverseRelationShadowVariable(sourceVariableName = "employee")
public List<ShiftAssignment> getEmployeeAssignedToShiftAssignments() {
return employeeAssignedToShiftAssignments;
}
ShiftAssignment:
#PlanningVariable(valueRangeProviderRefs = {
"employeeRange" }, strengthComparatorClass = EmployeeStrengthComparator.class,nullable = true)
public Employee getEmployee() {
return employee;
}
// the value range for this planning entity
#ValueRangeProvider(id = "employeeRange")
public List<Employee> getPossibleEmployees() {
return getShift().getEmployeesThatCanWorkThisShift();
}
NurseRoster:
#ValueRangeProvider(id = "employeeRange")
#PlanningEntityCollectionProperty
public List<Employee> getEmployeeList() {
return employeeList;
}
And this is the method i use to update that listOfEmployeesThatCanWorkThisShift:
public static void checkIfAnEmployeeCanBelongInGivenShiftAssignmentValueRange(NurseRoster nurseRoster) {
List<Shift> shiftList = nurseRoster.getShiftList();
List<Employee> employeeList = nurseRoster.getEmployeeList();
for (Shift shift : shiftList) {
List<Employee> employeesThatCanWorkThisShift = new ArrayList<>();
String shiftDate = shift.getShiftDate().getDateString();
ShiftTypeDefinition shiftTypeDefinitionForShift = shift.getShiftType().getShiftTypeDefinition();
for (Employee employee : employeeList) {
AgentDailySettings agentDailySetting = SearchThroughSolution.findAgentDailySetting(employee, shiftDate);
List<ShiftTypeDefinition> shiftTypeDefinitions = agentDailySetting.getShiftTypeDefinitions();
if (shiftTypeDefinitions.contains(shiftTypeDefinitionForShift)) {
employeesThatCanWorkThisShift.add(employee);
}
}
shift.setEmployeesThatCanWorkThisShift(employeesThatCanWorkThisShift);
}
}
And the rule that i use:
rule "maxDaysInPeriod"
when
$shiftAssignment : ShiftAssignment(employee != null)
then
int differentDaysInPeriod = MethodsUsedInScoreCalculation.employeeMaxDaysPerPeriod($shiftAssignment.getEmployee());
int maxDaysInPeriod = $shiftAssignment.getEmployee().getAgentPeriodSettings().getMaxDaysInPeriod();
if(differentDaysInPeriod > maxDaysInPeriod)
{
scoreHolder.addHardConstraintMatch(kcontext, differentDaysInPeriod - maxDaysInPeriod);
}
end
How can i fix this error?

This has definitely something to do with the solution cloning that is happening when a "new best solution" is created.
I encountered the same error when i implemented custom solution cloning. In my project i have multiple planning entity classes and all of them have references to each other (either a single value or a List). So when solution cloning is happening the references need to be updated so they can point to the cloned values. This is something that the default cloning process is doing without a problem, and thus leaving the solution in a consistent state. It even updates the Lists of planning entity instances in the parent planning entities correctly (covered by the method "cloneCollectionsElementIfNeeded" from the class "FieldAccessingSolutionCloner" from the OptaPlanner core).
Just a demonstration what i have when it comes to the planning entity classes:
#PlanningEntity
public class ParentPlanningEntityClass{
List<ChildPlanningEntityClass> childPlanningEntityClassList;
}
#PlanningEntity
public class ChildPlanningEntityClass{
ParentPlanningEntityClass parentPlanningEntityClass;
}
At first i did not update any of the references and got the error even for "ChildPlanningEntityClass". Then i have written the code that updates the references. When it comes to the planning entity instances that were coming from the class "ChildPlanningEntityClass" everything was okay at this point because they were pointing to the cloned object. What i did wrong in the "ParentPlanningEntityClass" case was that i did not create the "childPlanningEntityClassList" list from scratch with "new ArrayList();", but instead i just updated the elements of the list (using the "set" method) to point at the cloned instances of the "ChildPlanningEntityClass" class. When creating a "new ArrayList();", filling the elements to point to the cloned objects and setting the "childPlanningEntityClassList" list everything was consistent (tested with FULL_ASSERT).
So just connecting it to my issue maybe the list "employeeAssignedToShiftAssignments" is not created from scratch with "new ArrayList();" and elements instead just get added or removed from the list. So what could happen (if the list is not created from scratch) here is that both the working and the new best solution (the clone) will point to the same list and when the working solution would continue to change this list it would corrupt the best solution.

Related

The entity was never added to this scoreDirector exception during custom cloning

I'm trying to implement custom cloning in my solution, i followed the instructions as in the documentation, and i encountered a roadblock in the form of this exception : The entity was never added to this ScoreDirector. Maybe that specific instance is not in the return values of the PlanningSolution's entity members. I know that this is not true because before the custom cloning, this exception wasn't thrown.
My planningClone method is setup like this :
#Override
public Solution planningClone() {
Solution clonedSolution = new Solution();
clonedSolution.id = id;
clonedSolution.code = code;
clonedSolution.score = score;
clonedSolution.field1 = field1;
clonedSolution.field2 = field2;
...............
clonedSolution.fieldN = fieldN;
List<PlanningEntity1> clonedPlanningEntity1List= new ArrayList<PlanningEntity1>(planningEntity1List.size());
List<PlanningEntity2> clonedPlanningEntity2List= new ArrayList<PlanningEntity2>(planningEntity1List.size());
for (PlanningEntity1 planningEntity: planningEntity1List) {
clonedPlanningEntity1List.add(planningEntity.clone());
}
for (PlanningEntity2 planningEntity: planningEntity2List) {
clonedPlanningEntity1List.add(planningEntity.clone());
}
clonedSolution.planningEntity1List = clonedPlanningEntity1List;
clonedSolution.planningEntity2List = clonedPlanningEntity2List;
return clonedSolution;
{
The clone method for my planning entities is implemented through the Java interface Cloneable:
protected PlanningEntity clone() {
try {
return (PlanningEntity) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
Just to be sure, i checked every entity instance and their collections to make sure my cloning was working correctly, and it in fact is.
What step am i missing here?
If there is one planning entity pointing to another planning entity from a different class or maybe pointing to a list, than the cloning process needs to take care of the references for those planning entities so they point to the cloned objects.This is something that the default cloning process is doing without a problem, and thus leaving the solution in a consistent state. It even updates the Lists of planning entity instances in the parent planning entities correctly (covered by the method "cloneCollectionsElementIfNeeded" from the class "FieldAccessingSolutionCloner" from the OptaPlanner core).
So for example if we have the next two planning entity classes
#PlanningEntity
public class ParentPlanningEntityClass{
List<ChildPlanningEntityClass> childPlanningEntityClassList;
}
#PlanningEntity
public class ChildPlanningEntityClass{
ParentPlanningEntityClass parentPlanningEntityClass;
}
The "parentPlanningEntityClass" variable needs to be set to point to the cloned object. When it comes to the list "childPlanningEntityClassList" it first needs to be created from scratch with "new ArrayList();" so that both the working and new best solution (the one that is currently getting cloned) don't point to the same list. At the end the newly created list needs to be filled with the cloned objects.

Create new records when searching for reference object

In my current project I would like to be able to create new objects when searching for a reference object. This happens in several places of the application.
For example, let's assume we have a City Entity and a Country Entity. The City entity has a mandatory reference to the Country entity.
In my use case, I would like to create a new City. When I do this, I will have to assign a Country to the new City. When I click on the lookup icon, I get the selection dialog with all existent countries. But if I don't have the Country I want, I have to abort the operation, get back to the countries list and create the new one I'd like to assign to my new city.
Would it be possible to create that new Country from the selection dialog with all countries?
If it is possible, is the country being added to the list right after it has been created?
Would it be possible to one define a range for the countries list? For example, showing only countries in Europe, if the user is in Europe.
I could imagine, that this would be a lot to ask from the framework. But I am just giving a shot and perhaps also giving a new feature idea, which would be nice to have.
Customization of the LOV dialog :
You can easily customize the LOV dialog by creating your own class of the LOV action that is installed next to the reference fields.
Adding a new action in the dialog (the create action) :
public class LovActionWithCreate<E, F, G> extends LovAction<E, F, G> {
private IDisplayableAction createAction;
#Override
protected void feedContextWithDialog(IReferencePropertyDescriptor<IComponent> erqDescriptor,
IQueryComponent queryComponent, IView<E> lovView, IActionHandler actionHandler,
Map<String, Object> context) {
super.feedContextWithDialog(erqDescriptor, queryComponent, lovView, actionHandler, context);
List<IDisplayableAction> defaultLovDialogActions = (List<IDisplayableAction>) context.get(
ModalDialogAction.DIALOG_ACTIONS);
defaultLovDialogActions.add(1, getCreateAction());
}
/**
* Gets create action.
*
* #return the create action
*/
protected IDisplayableAction getCreateAction() {
return createAction;
}
/**
* Sets create action.
*
* #param createAction
* the create action
*/
public void setCreateAction(IDisplayableAction createAction) {
this.createAction = createAction;
}
}
The key point is to override the feedContextWithDialog method in order to install the new action into the dialog.
Next step is to install your new LOV action. You can either do it globally for whole application or per reference view :
replacing the LOV action globally is just a matter of declaring an action named 'lovAction' into your application frontend.groovy, i.e. :
action('lovAction', parent: 'lovActionBase', class:'test.LovActionWithCreate',
custom: [createAction_ref:'theCreateAction']
)
replacing the LOV action on a certain reference field in a form can be done by using the referencePropertyView (in a form or in a table) and its 'lovAction' property, e.g. :
action('lovActionWithCreate', parent: 'lovActionBase', class:'test.LovActionWithCreate',
custom: [createAction_ref:'theCreateAction']
)
form('ACertainForm'){
fields {
...
referencePropertyView name:'country', lovAction:'lovActionWithCreate'
...
}
}
Creating an entity in the LOV dialog :
In the next step, we create the action that will be responsible for opening an extra dialog in order to create the new entity, persist it and, if successful, add it to the LOV result view. This is a little more complicated but not that much.
First of all, we have to open a new dialog.
For doing this, we will inherit the built-in EditComponentAction. The goal of this action is to edit a model in a modal dialog. The only difficulty here is that our model is only known at runtime. No problem though as we will use the dynamic nature of Jspresso.
public class CreateEntityFromLOVAction<E, F, G> extends EditComponentAction<E,F,G> {
#Override
protected Object getComponentToEdit(Map<String, Object> context) {
IEntityFactory entityFactory = getBackendController(context).getEntityFactory();
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
Class<IEntity> entityToCreateContract = lovQueryComponent.getQueryContract();
IEntity entityInstance = entityFactory.createEntityInstance(entityToCreateContract);
setActionParameter(Arrays.asList(entityInstance), context);
return entityInstance;
}
#Override
protected IViewDescriptor getViewDescriptor(Map<String, Object> context) {
IEntityFactory entityFactory = getBackendController(context).getEntityFactory();
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
Class<IEntity> entityToCreateContract = lovQueryComponent.getQueryContract();
IComponentDescriptor<?> entityToCreateDescriptor = entityFactory.getComponentDescriptor(entityToCreateContract);
BasicComponentViewDescriptor formViewDescriptor = new BasicComponentViewDescriptor();
formViewDescriptor.setModelDescriptor(entityToCreateDescriptor);
return formViewDescriptor;
}
}
If you look at the code above, our new action takes care of the following :
Get the type of entity to create from the context. For this, we are just exploring the query component which is the model of the LOV dialog.
Create the entity instance and set it as action parameter in the context for the chain to continue working on it (save, close dialog).
Create a form to display in the creation dialog.
Points 1 and 2 are handled by the getComponentToEdit method and point 3 by the getViewDescriptor method.
Next, when the user clicks Ok, we have to save the entity, add it to the LOV result list and close the creation dialog.
For this, we will create a new action and chain it to the saveAction and closeDialogAction built-in actions.
public class CreateEntityFromLOVPersistAction<E, F, G> extends FrontendAction<E,F,G> {
#Override
public boolean execute(IActionHandler actionHandler, Map<String, Object> context) {
if (super.execute(actionHandler, context)) {
IQueryComponent lovQueryComponent = (IQueryComponent) context.get(IQueryComponent.QUERY_COMPONENT);
List<IEntity> createdEntityInstance = getActionParameter(context);
lovQueryComponent.setQueriedComponents(createdEntityInstance);
return true;
}
return false;
}
}
And the final wiring in SJS frontend.groovy:
action('createEntityFromLovOkAction', parent: 'okDialogFrontAction',
class:'test.CreateEntityFromLOVPersistAction',
wrapped: 'saveBackAction', next: 'closeDialogAction')
action('createEntityFromLovAction', parent: 'editComponentAction',
class: 'test.CreateEntityFromLOVAction',
name:'add.name', custom: [
okAction_ref: 'createEntityFromLovOkAction'
]
)
action('lovAction', parent: 'lovActionBase',
class:'test.LovActionWithCreate',
custom: [createAction_ref:'createEntityFromLovAction']
)
A long answer for less than 100 lines of code, but now you have a fully generic LOV action where the user can create any missing master data without leaving his current screen.
Presetting some data in the LOV filter depending on the user context :
For this, we generally use the initialization mapping that allows for setting some restrictions (either static or dynamic) on a reference property when it is queried in a LOV. For instance, let's consider the following use case :
You have 2 entities, Contract and Tariff, that are linked together through a 1-N relationship, i.e. a Contract is linked to 1 Tariff.
Contract and Tariff both have a country property and a Tariff can be assigned to a Contract if and only if they belong to the same country.
Tarrif has a status property and can only be used in a Contract if its status is ACTIVE.
You can simply enforce these rules in the LOV by setting the initialization mapping on the reference property the following way :
Entity('Contract', ...) {
...
reference 'tariff', ref: 'Tariff',
initializationMapping: [
'country': 'country',
'status': 'ACTIVE'
]
...
}
Thinking about it, this kind of behavior might very well find its way to the framework, so please, feel free to ope an enhancement request in the Jspresso GitHub.

MVC 3/EF repository pattern and proper data access

Being rather new to MVC 3 and EF, I'm trying to understand the best architectural approach to developing an application for my company. The application will be a large-scale application that potentially handles hundreds of users at the same time, so I want to make sure I understand and am following proper procedures. So far, I've determined that a simple repository pattern (such as Controller -> Repository -> EF) approach is the best and easiest to implement, but I'm not sure if that is definitely the best way to do things. The application will basically return data that is shown to a user in a devexpress grid and they can modify this data/add to it etc.
I found this article and it is rather confusing for me at this time, so I'm wondering if there is any reason to attempt to work with a disconnected EF and why you would even want to do so: http://www.codeproject.com/Articles/81543/Finally-Entity-Framework-working-in-fully-disconne?msg=3717432#xx3717432xx
So to summarize my question(s):
Is the code below acceptable?
Should it work fine for a large-scale MVC application?
Is there a better way?
Will unnecessary connections to SQL remain open from EF? (SQL Profiler makes it look like it stays open a while even after the using statement has exited)
Is the disconnected framework idea a better one and why would you even want to do that? I don't believe we'll need to track data across tiers ...
Note: The repository implements IDisposable and has the dispose method listed below. It creates a new instance of the entity context in the repository constructor.
Example Usage:
Controller (LogOn using Custom Membership Provider):
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
User newUser = new User();
using (AccountRepository repo = new AccountRepository())
{
newUser = repo.GetUser(model.UserName);
...
}
}
Membership Provider ValidateUser:
public override bool ValidateUser(string username, string password)
{
using (AccountRepository repo = new AccountRepository())
{
try
{
if (string.IsNullOrEmpty(password.Trim()) || string.IsNullOrEmpty(username.Trim()))
return false;
string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");
bool exists = false;
exists = repo.UserExists(username, hash);
return exists;
}catch{
return false;
}
}
}
Account Repository Methods for GetUser & UserExists:
Get User:
public User GetUser(string userName)
{
try
{
return entities.Users.SingleOrDefault(user => user.UserName == userName);
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
User Exists:
public bool UserExists(string userName, string userPassword)
{
if (userName == "" || userPassword == "")
throw new ArgumentException(InvalidUsernamePassword);
try
{
bool exists = (entities.Users.SingleOrDefault(u => u.UserName == userName && u.Password == userPassword) != null);
return exists;
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
Repository Snippets (Constructor, Dispose etc):
public class AccountRepository : IDisposable
{
private DbContext entities;
public AccountRepository()
{
entities = new DbContext();
}
...
public void Dispose()
{
entities.Dispose();
}
}
What's acceptable is pretty subjective, but if you want to do proper data access I suggest you do NOT use the repository pattern, as it breaks down as your application gets more complex.
The biggest reason is minimizing database access. So for example look at your repository and notice the GetUser() method. Now take a step back from the code and think about how your application is going to be used. Now think about how often you are going to request data from the user table without any additional data. The answer is almost always going to be "rarely" unless you are creating a basic data entry application.
You say it your application will show a lot of grids. What data is in that Grid? I'm assuming (without knowing your application domain) that the grids will combine user data with other information that's relevant for that user. If that's the case, how do you do it with your repositories?
One way is to call on each repository's method individually, like so:
var user = userRepository.GetUser("KallDrexx");
var companies = companyRepository.GetCompaniesForUser(user.Id);
This now means you have 2 database calls for what really should be just one. As your screens get more and more complex, this will cause the number of database hits to increase and increase, and if your application gets significant traffic this will cause performance issues. The only real way to do this in the repository pattern is to add special methods to your repositories to do that specific query, like:
public class UserRepository
{
public User GetUser(string userName)
{
// GetUser code
}
public User GetUserWithCompanies(string userName)
{
// query code here
}
}
So now what happens if you need users and say their contact data in one query. Now you have to add another method to your user repository. Now say you need to do another query that also returns the number of clients each company has, so you need to add yet another method (or add an optional parameter). Now say you want to add a query that returns all companies and what users they contain. Now you need a new query method but then comes the question of do you put that in the User repository or the Company repository? How do you keep track of which one it's in and make it simple to choose between GetUserWithCompany and GetCompanyWithUsers when you need it later?
Everything gets very complex from that point on, and it's those situations that have made me drop the repository pattern. What I do now for data access is I create individual query and command classes, each class represents 1 (and only 1) query or data update command to the database. Each query class returns a view model that only contains the data I need for one specific user usage scenario. There are other data access patterns that will work too (specification pattern, some good devs even say you should just do your data access in your controllers since EF is your data access layer).
The key to doing data access successfully is good planning. Do you know what your screens are going to look like? Do you know how users are going to use your system? Do you know all the data that is actually going to be on each screen? If the answer to any of these is no, then you need to take a step back and forget about the data layer, because the data layer is (or should be for a good application) determined based on how the application is actually going to be used, the UI and the screens should not be dependent on how the data layer was designed. If you don't take your UI needs and user usage scenarios into account when developing the data access, your application will not scale well and will not be performant. Sometimes that's not an issue if you don't plan on your site being big, but it never hurts to keep those things in mind.
No matter what you do, you may consider moving instantiation and disposing of your context to your controller like this:
public class MyController : Controller
{
private Entities context = new Entities();
...
public override void Dispose()
{
context.Dispose();
}
}
You can then pass that context into any method that needs it without duplicating the overhead of creating it.
I disagree that the repository pattern is necessarily bad for the same reason. You create multiple classes to break up your code to make it manageable and still reuse the same context. That could look something like this:
repository.Users.GetUser(userName);
In this case "Users" is a lazy loaded instance of your user repository class which reuses the context from your repository. So the code for that Users property in your repository would look something like this:
private UserRepository users;
public UserRepository Users
{
get
{
If (users == null)
{
users = new UserRepository(this);
}
return users;
}
}
You can then expose your context to these other lazy loaded classes via a property.
I don't think this necessarily conflicts with KallDrexx's pattern. His method simply flips this so instead of
repository.Users.GetUser(userName);
You would have something like
UserQuery query = new UserQuery(repository.Users);
This then becomes an issue of syntax. Do you want this:
repository.Area.Query(value1, value2, ...);
Or this:
AreaQuery query = new AreaQuery { Property1 = value1, ... };
The latter actually works nicer with model binding but obviously is more verbose when you actually have to code it.
Best advice KallDrexx gave is to just put your code I your actions and then figure it out. If you are doing simple CRUD, then let MVC instantiate and populate your model, then all you have to do is attach and save. If you find you can reuse code, move it to where it can be reused. If your application starts getting too complicated, try some of these recommendations until you find what works for you.

Does NHibernate really deliver transparent persistency

Starting to use Nhibernate for persistency being seduced by the promise that it respects your domain model, I tried to implement a relation manager for my domain objects. Basically, to DRY my code with respect to managing bidirectional one to many and many to many relations, I decided to have those relations managed by a separate class. When a one to many or many to one property is set an entry for the two objects is made in an dictionary, the key is either a one side with a collection value to hold the many sides, or a many side with a value of the one side.
A one to many relation for a specific combination of types looks as follows:
public class OneToManyRelation<TOnePart, TManyPart> : IRelation<IRelationPart, IRelationPart>
where TOnePart : class, IRelationPart
where TManyPart : class, IRelationPart
{
private readonly IDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>> _oneToMany;
private readonly IDictionary<TManyPart, TOnePart> _manyToOne;
public OneToManyRelation()
{
_manyToOne = new ConcurrentDictionary<TManyPart, TOnePart>();
_oneToMany = new ConcurrentDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>>();
}
public void Set(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
}
public void Add(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
_oneToMany[onePart].Add(manyPart);
}
public Iesi.Collections.Generic.ISet<TManyPart> GetManyPart(TOnePart onePart)
{
if (!_oneToMany.ContainsKey(onePart)) _oneToMany[onePart] = new HashedSet<TManyPart>();
return _oneToMany[onePart];
}
public TOnePart GetOnePart(TManyPart manyPart)
{
if(!_manyToOne.ContainsKey(manyPart)) _manyToOne[manyPart] = default(TOnePart);
return _manyToOne[manyPart];
}
public void Remove(TOnePart onePart, TManyPart manyPart)
{
_manyToOne.Remove(manyPart);
_oneToMany[onePart].Remove(manyPart);
}
public void Set(TOnePart onePart, Iesi.Collections.Generic.ISet<TManyPart> manyPart)
{
if (onePart == null) return;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, manyPart);
else _oneToMany[onePart] = manyPart;
}
public void Clear(TOnePart onePart)
{
var list = new HashedSet<TManyPart>(_oneToMany[onePart]);
foreach (var manyPart in list)
{
_manyToOne.Remove(manyPart);
}
_oneToMany.Remove(onePart);
}
public void Clear(TManyPart manyPart)
{
if (!_manyToOne.ContainsKey(manyPart)) return;
if (_manyToOne[manyPart] == null) return;
_oneToMany[_manyToOne[manyPart]].Remove(manyPart);
_manyToOne.Remove(manyPart);
}
}
On the many side a code snippet looks like:
public virtual SubstanceGroup SubstanceGroup
{
get { return RelationProvider.SubstanceGroupSubstance.GetOnePart(this); }
protected set { RelationProvider.SubstanceGroupSubstance.Set(value, this); }
}
On the one side, so, in this case the SubstanceGroup, the snippet looks like:
public virtual ISet<Substance> Substances
{
get { return RelationProvider.SubstanceGroupSubstance.GetManyPart(this); }
protected set { RelationProvider.SubstanceGroupSubstance.Set(this, value); }
}
Just using my domain objects, this works excellent. In the domain object I just have to reference an abstract factory that retrieves the appropriate relation and I can set the relation from one side, wich automatically becomes thus bidirectional.
However, when NH kicks in the problem is that I get duplicate keys in my dictionaries. Somehow NH sets a relation property with a null value(!) with a new copy(?) of a domain object. So when the domain object gets saved, I have two entries of that domain object in, for example the many side of the relation, i.e. _manyToOne dictionary.
This problem makes me lose my hair, I do not get it what is happening??
To answer your first, very general question: "Does NHibernate really deliver transparent persistency", I just can say: nothing is perfect. NH tries its best to be as transparent as possible, by also trying to keep its complexity as low as possible.
There are some assumptions, particularly regarding collections: Collections and their implementations are not considered to be part of your domain model. NH provides its own collection implementations. You are not only expected to use the interfaces like ISet and IList. You should also take the instances given by NH when the object is read from the database and never replace it with your own. (I don't know what your relation class is actually used for, so I don't know if this is the problem here.)
Domain objects are unique within the same instance of the session. If you get new instances of domain objects each time, you probably implemented the "session-per-call" anti-pattern, which creates a new session for each database interaction.
I don't have a clue what you actually are doing. How is this OneToManyRelation actually used for? What are you doing when NH doesn't behave as expected? This is a very specific problem to your specific implementation.
Besides the comments on 'convoluted code' and 'what the heck are you doing'. The problem was that I was replacing the persistence collections of NH like in the below code snippet:
public void Add(TOnePart onePart, TManyPart manyPart)
{
if (onePart == null || manyPart == null) return;
if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
else _manyToOne[manyPart] = onePart;
if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
_oneToMany[onePart].Add(manyPart);
}
I create a new Hashed set for the many part. And that was the problem. If just has set the many part with the collection coming in (in case of the persistence collection implementation of NH) than it would have worked.
As a NH newbie, this replacing of collections with a special implementation from NH has been an important source of errors. Just as a warning to other NH newbies.

Refreshing an entity throws an NHibernate.UnresolvableObjectException

The entities and mappings I'm talking about in this question can be found here :)
Here is the context:
I have a parent view-model which helps to manage some entities, and which has its own session.
From this VM, I open another view-model (with its own session too), do some changements to the entity (add and/or remove children), and when I validate the changements, I commit the session and warns the first view-model to refresh the display:
public void Validate()
{
using (var tx = Session.BeginTransaction())
{
try
{
SelectedTeam.ClearRoster();
foreach (var teamPlayer in TeamPlayers)
SelectedTeam.AddPlayer(teamPlayer);
teamsRepository.SaveOrUpdate(SelectedTeam);
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
}
finally
{
if (tx.WasCommitted)
ServiceLocator.Current.GetInstance<Mediator>().NotifyColleagues(MediatorMessages.DisplayEntityInfos, SelectedTeam.Id);
}
}
}
Here is the faulted method of the parent VM:
public void RefreshEntitiesListAndDisplayEntityInfos(int selectedEntityId)
{
TEntity entity = entitiesRepository.Load(selectedEntityId);
Session.Refresh(entity);
//...
}
The exception is thrown at the Refresh line:
NHibernate.UnresolvableObjectException
And the message is:
No row with the given identifier exists[Emidee.CommonEntities.PlayerInTeam#3
I can open and change the entity multiple times, but it seems that the exception is thrown when I delete a children, then add another one, and finally delete another one.
After some readings on the web, it seems that's because when I refresh the entity, and because I changed the HasMany relationship (because I have deleted a player for example), NH tries to reload the deleted row.
I've tried to add a NotFound.Ignore statement on the HasMany in my mappings, I've tried to force a new query to the DB instead of a Load, but I still get this exception.
Does someone know how I could fix that?
Thanks in advance
Mike
This is a known behavior when refreshing objects with modified collections.
To force reload, change your method to do session.Evict with the entity as a parameter. This is the code we use in our base model class:
public T ReGet<T>(T entity) where T : IEntity
{
var id = entity.Id;
Session.Evict(entity);
return Session.Get<T>(id);
}
Well, I've just spotted the problem.
To update the players list of the team, I used to clear the list, and add the new players, before updating the entity.
Now, I update the list by removing and adding only the players who have been moved by the user, and I don't have any problems at all now.
That's weird. I don't know what was wrong before, but as least that works now.