Unable to delete emloyees from school...

Topics: Writing modules
Dec 16, 2012 at 12:29 PM

Hey everyone,

I have a weird problem, i created my own module to add schools, employees on schools for a project. I followed the video's from pluralsight.

Everything goes wright when I add employees to a school (it uses javascript to add and remove employees) but when I delete employees and click on save the employees are still there.

The logic seems ok, the javascript works, employees are deleted if you remove them but when you click on save they are back so I guess they are not deleted from the database.

This is the code from my IschoolService:

using Orchard;
using CLB.SchoolModule.Models;
using CLB.SchoolModule.ViewModels;
using Orchard.Data;
using System.Linq;

namespace CLB.SchoolModule.Services
{
    public interface ISchoolService : IDependency
    {
        void UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part);
    }

    public class SchoolService : ISchoolService
    {

        private readonly IRepository _medewerkerPartRecordRepository;
        private readonly IRepository _schoolMedewerkerRecordRepository;

        public SchoolService(
            IRepository medewerkerPartRecordRepository,
            IRepository schoolMedewerkerRecordRepository)
        {
            _medewerkerPartRecordRepository = medewerkerPartRecordRepository;
            _schoolMedewerkerRecordRepository = schoolMedewerkerRecordRepository;
        }
        
        public void UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part)
        {
            part.Naam = viewModel.Naam;
            part.EmailAdres = viewModel.EmailAdres;
            part.FaxNr = viewModel.FaxNr;
            part.Straat = viewModel.Straat;
            part.TelNr = viewModel.TelNr;
            part.Website = viewModel.Website;
            part.Directie = viewModel.Directie;
            part.SchoolTypeRecord = viewModel.SchoolTypeRecord;
            part.StadRecord = viewModel.StadRecord;

            var oldCast = _schoolMedewerkerRecordRepository.Fetch(sm => sm.SchoolPartRecord.Id == part.Id).Select(r => r.MedewerkerPartRecord.Id).ToList();
            foreach (var oldMedewerkerId in oldCast.Except(viewModel.Medewerkers))
            {
                _schoolMedewerkerRecordRepository.Delete(_schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldMedewerkerId));
            }
            foreach (var newMedewerkerId in viewModel.Medewerkers.Except(oldCast))
            {
                var medewerker = _medewerkerPartRecordRepository.Get(newMedewerkerId);
                _schoolMedewerkerRecordRepository.Create(new SchoolMedewerkerRecord { MedewerkerPartRecord = medewerker, SchoolPartRecord = part.Record });
            }
        }
    }
}

Does anyone know what the problem could be?

Kind regards,

Borrie

Coordinator
Dec 16, 2012 at 11:07 PM

You're deleting records instead of deleting content items. If those are part records, you should always be acting on content items instead of directly on the records, for creation as well as deletion.

Dec 17, 2012 at 8:25 AM

Betrand,

Do you know the syntax to do a delete? The create( new schoolmederkerRecord .. works

but for the delete I don't know the syntax

 

Borrie

Developer
Dec 17, 2012 at 8:55 AM

Actually you should use an IContentManager to create and delete your content items. It has a New, Create and a Remove method. Use the New method to new up a new content item of a specific content type, then use Create to save that content item (or use Create directly to new up a content item and pass in a lambda to configure the new content item in one statement). Use Remove to delete the content item (keep in mind that this will be a "soft" delete; the content item will still be there in the DB, unpublished).

Dec 17, 2012 at 10:08 AM

Well actually this is a many to many relation, so I'm not deleting content items, I'm adding (which works) to a joint table and want to do the same with the delete.

It's really strange because in the pluralsight example it works.

Borrie

Developer
Dec 17, 2012 at 10:34 AM
Edited Dec 17, 2012 at 10:35 AM

Ah I see. Ok, so did you step into the debugger to make sure that the call to:

_schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldMedewerkerId)

actually returns an existing record? Just looking at the code, nothing strikes me as strange, and deleting records has always worked for me, so perhaps there's a problem with the modelbinding. And to be on the safe side, I would turn on Managed Debugging Assistents to detect any catched exceptions, e.g. related to DB errors.


Dec 17, 2012 at 11:48 AM

When I check the logs I see this: I guess that's the problem :) Now to find a way how to fix it :)

2012-12-17 12:42:57,563 [9] NHibernate.Impl.AbstractSessionImpl - DTC transaction prepre phase failed
NHibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations)[CLB.SchoolModule.Models.SchoolMedewerkerRecord#2]
   at NHibernate.Impl.SessionImpl.ForceFlush(EntityEntry entityEntry)
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
   at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event)
   at NHibernate.Impl.SessionImpl.SaveOrUpdate(String entityName, Object obj)
   at NHibernate.Engine.CascadingAction.SaveUpdateCascadingAction.Cascade(IEventSource session, Object child, String entityName, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeToOne(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeAssociation(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeProperty(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeCollectionElements(Object child, CollectionType collectionType, CascadeStyle style, IType elemType, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeCollection(Object child, CascadeStyle style, Object anything, CollectionType type)
   at NHibernate.Engine.Cascade.CascadeAssociation(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeProperty(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
   at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
   at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything)
   at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session)
   at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
   at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
   at NHibernate.Impl.SessionImpl.Flush()
   at NHibernate.Transaction.AdoNetWithDistrubtedTransactionFactory.DistributedTransactionContext.System.Transactions.IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)

This is my schoolmedewerkerRecord.cs:

namespace CLB.SchoolModule.Models
{
    public class SchoolMedewerkerRecord
    {
        public virtual int Id { get; set; }
        public virtual SchoolPartRecord SchoolPartRecord { get; set; }
        public virtual MedewerkerPartRecord MedewerkerPartRecord { get; set; }
    }
}

Developer
Dec 17, 2012 at 11:54 AM
Edited Dec 17, 2012 at 11:54 AM

Could it be that SchoolPartRecord and or MedewerkerPartRecord have a collection property of type SchoolMedewerkerRecord objects? If that's the case, be sure to remove the SchoolMedewerkerRecord from these collections first before removing it from the repository.

Dec 17, 2012 at 12:29 PM

Skywalker,

That is indeed the case, however I think I use these list for other functions also so I'm not sure if deleting them is a wise thing to do :)

Is the CascadeAllDeleteOrphan setting correct?

using System.Collections.Generic;
using Orchard.Data.Conventions;
using Orchard.ContentManagement.Records;

namespace CLB.SchoolModule.Models
{
    public class SchoolPartRecord : ContentPartRecord
    {

        public SchoolPartRecord()
        {
            SchoolMedewerkers = new List();
        }
        
        public virtual StadRecord StadRecord { get; set; }
        public virtual SchoolTypeRecord SchoolTypeRecord { get; set; }
        public virtual string Naam { get; set; }
        public virtual string Straat { get; set; }
        public virtual string TelNr { get; set; }
        public virtual string FaxNr { get; set; }
        public virtual string Website { get; set; }
        public virtual string EmailAdres { get; set; }
        public virtual string Directie { get; set; }

        [CascadeAllDeleteOrphan]
        public virtual IList SchoolMedewerkers { get; set; }

             
     }
}
using Orchard.ContentManagement.Records;
using System.Collections.Generic;
using Orchard.Data.Conventions;
namespace CLB.SchoolModule.Models
{
    public class MedewerkerPartRecord : ContentPartRecord
    {
        
        public MedewerkerPartRecord() 
        {
            MedewerkerScholen = new List();
        }
        
        public virtual string AchterNaam { get; set; }
        public virtual string VoorNaam { get; set; }
        public virtual string EmailAdres { get; set; }
        public virtual string Telnr { get; set; }
        public virtual FunctieRecord FunctieRecord { get; set; }

        [CascadeAllDeleteOrphan]
        public virtual IList MedewerkerScholen { get; set; }
    }
}

Developer
Dec 17, 2012 at 12:40 PM

I'm not suggesting that you should remove the lists in their entirety, only that you remove the reference to the record you are trying to delete from these lists. You see, if these collections contain a reference to a record that you deleted, you will get the exception ObjectDeletedException as you just saw.

As for the CascadeAllDeleteOrphan, I am afraid I'm not familiar with that attribute, but if I remember correctly it should cause all records in the MedewerkerScholen property to be deleted when you delete the MedewerkerPartRecord instance containing them.

However, that will never happen in this case, since MedewerkerPartRecord is a content part record, which suggests that you will only remove content items containing this content part. And since removing a content item won't physically remove the content item, none of the content part records will ever be removed and thus the CascadeAllDeleteOrphan attribute will never kick in.

So what I would do instead is implement the OnRemoved content event in a content handler for the MedewerkerPart and manually delete the MedewerkerScholen instances from there.

Dec 17, 2012 at 2:37 PM

Oooow God :)

I found the problem, I had to remove the  [CascadeAllDeleteOrphan] from the SChoolpartRecord for the IList...

I don't know why i put it there in the first place...

There should only be a  [CascadeAllDeleteOrphan] on the list in Medewerkerpartrecord...

Thanks for taking the time to investigate and sorry for wasting yours...

Now all my errors are gone and I've succesfully upgraded my solution to 1.6

Borrie

Developer
Dec 17, 2012 at 4:28 PM

No worries, the worst thing that can happen is that we all learn something here :) Glad you figured it out.

Dec 17, 2012 at 4:32 PM

Well, in the mean time, the delete works, except for when I try to delete if there's only one employee added to a school :)

Then I get a null error but I don't know where it comes from:

2012-12-17 16:13:12,348 [15] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator - ArgumentNullException thrown from IContentPartDriver by CLB.SchoolModule.Drivers.SchoolPartDriver
System.ArgumentNullException: Value cannot be null.
Parameter name: second
   at System.Linq.Enumerable.Except[TSource](IEnumerable`1 first, IEnumerable`1 second)
   at CLB.SchoolModule.Services.SchoolService.UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part)
   at CLB.SchoolModule.Drivers.SchoolPartDriver.Editor(SchoolPart part, IUpdateModel updater, Object shapeHelper)
   at Orchard.ContentManagement.Drivers.ContentPartDriver`1.Orchard.ContentManagement.Drivers.IContentPartDriver.UpdateEditor(UpdateEditorContext context) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\ContentManagement\Drivers\ContentPartDriver.cs:line 79
   at Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator.<>c__DisplayClass10.<UpdateEditor>b__f(IContentPartDriver driver) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\ContentManagement\Drivers\Coordinators\ContentPartDriverCoordinator.cs:line 63
   at Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable`1 events, Action`1 dispatch, ILogger logger) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\InvokeExtensions.cs:line 17

Dec 17, 2012 at 9:02 PM

Skywalker,

I just did some tests on a more populated database.

Now when I try to delete an employee from a school I get this error:

2012-12-17 21:21:33,070 [10] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator - InvalidOperationException thrown from IContentPartDriver by CLB.SchoolModule.Drivers.SchoolPartDriver
System.InvalidOperationException: Sequence contains more than one element
   at NHibernate.Linq.DefaultQueryProvider.ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery)
   at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
   at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source)
   at Orchard.Data.Repository`1.Get(Expression`1 predicate) in c:\Tortoise\src\Orchard\Data\Repository.cs:line 91
   at Orchard.Data.Repository`1.Orchard.Data.IRepository<T>.Get(Expression`1 predicate) in c:\Tortoise\src\Orchard\Data\Repository.cs:line 60
   at CLB.SchoolModule.Services.SchoolService.UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part)
   at CLB.SchoolModule.Drivers.SchoolPartDriver.Editor(SchoolPart part, IUpdateModel updater, Object shapeHelper)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute4[T0,T1,T2,T3,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
   at Orchard.ContentManagement.Drivers.ContentPartDriver`1.Orchard.ContentManagement.Drivers.IContentPartDriver.UpdateEditor(UpdateEditorContext context) in c:\Tortoise\src\Orchard\ContentManagement\Drivers\ContentPartDriver.cs:line 80
   at Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator.<>c__DisplayClass10.<UpdateEditor>b__f(IContentPartDriver driver) in c:\Tortoise\src\Orchard\ContentManagement\Drivers\Coordinators\ContentPartDriverCoordinator.cs:line 63
   at Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable`1 events, Action`1 dispatch, ILogger logger) in c:\Tortoise\src\Orchard\InvokeExtensions.cs:line 17

 

This is (I think) is because of the code:

            {
                _schoolMedewerkerRecordRepository.Delete(_schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldMedewerkerId));
            }

This works fine if there is only one MedewerkerId found in the whole table. Off course as it's a many to many so the medewerkerId is found a lot in the database.

Do you know the syntax to return the record with the combination of the part.Id and the medewerkerId

I'm not really a pro with these expressions :)

Borrie

Developer
Dec 17, 2012 at 9:48 PM

Ah, that makes sense. So what you need to do is retrieve the entire list of SchoolMedewerkerRecords that you want to delete, iterate over that list and delete each one of them. E.g.

// Select all medewerkers/school to delete
var medewerkersInScholen = _schoolMedewerkerRecordRepository.Fetch(r => r.MedewerkerPartRecord.Id == oldMedewerkerId).ToList();

// Delete the selected medewerkers/school records
foreach(var medewerkerInSchool in medewerkersInScholen){
   _schoolMedewerkerRecordRepository.Delete(medewerkerInSchool);
}

Dec 18, 2012 at 10:59 AM

Skywalker,

I've managed to create the correct code:

 var oldCast = _schoolMedewerkerRecordRepository.Fetch(sm => sm.SchoolPartRecord.Id == part.Id).Select(r => r.MedewerkerPartRecord.Id).ToList();
            
            foreach (var oldMedewerkerId in oldCast.Except(viewModel.Medewerkers))
            {
                var medewerkertodelete = _schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldMedewerkerId && r.SchoolPartRecord.Id == part.Id);
                _schoolMedewerkerRecordRepository.Delete(medewerkertodelete);
            }

This deletes the correct one, so I'm quite happy now :)

Now I still have the error if I remove an employee from a school and if that employee is the only one connected to that school I get a null reference error:

2012-12-18 11:49:46,435 [5] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator - ArgumentNullException thrown from IContentPartDriver by CLB.SchoolModule.Drivers.SchoolPartDriver
System.ArgumentNullException: Value cannot be null.
Parameter name: second
   at System.Linq.Enumerable.Except[TSource](IEnumerable`1 first, IEnumerable`1 second)
   at CLB.SchoolModule.Services.SchoolService.UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part)
   at CLB.SchoolModule.Drivers.SchoolPartDriver.Editor(SchoolPart part, IUpdateModel updater, Object shapeHelper)
   at Orchard.ContentManagement.Drivers.ContentPartDriver`1.Orchard.ContentManagement.Drivers.IContentPartDriver.UpdateEditor(UpdateEditorContext context) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\ContentManagement\Drivers\ContentPartDriver.cs:line 79
   at Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator.<>c__DisplayClass10.<UpdateEditor>b__f(IContentPartDriver driver) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\ContentManagement\Drivers\Coordinators\ContentPartDriverCoordinator.cs:line 63
   at Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable`1 events, Action`1 dispatch, ILogger logger) in C:\Documents and Settings\cig526\My Documents\Visual Studio 2010\Projects\CLB151\src\Orchard\InvokeExtensions.cs:line 17

 

Do you know what that could mean?

 

Developer
Dec 18, 2012 at 11:34 AM

I would launch the debugger and watch the exception occur, then inspect the variables on the faulting line. You'll find it.

Dec 18, 2012 at 3:22 PM

Ok, all is good now...

Seems I had to add some more code then suggested in the pluralsight video to get it working properly (not that I'm saying the video's aren't great because they helped me out a lot)

I had to do a check first to see if my oldcast of employees is one and that my employees in the viewmodel is null (if you delete the last one)

public void UpdateSchool(SchoolEditViewModel viewModel, SchoolPart part)
        {
            part.Naam = viewModel.Naam;
            part.EmailAdres = viewModel.EmailAdres;
            part.FaxNr = viewModel.FaxNr;
            part.Straat = viewModel.Straat;
            part.TelNr = viewModel.TelNr;
            part.Website = viewModel.Website;
            part.Directie = viewModel.Directie;
            part.SchoolTypeRecord = viewModel.SchoolTypeRecord;
            part.StadRecord = viewModel.StadRecord;

            var oldCast = _schoolMedewerkerRecordRepository.Fetch(sm => sm.SchoolPartRecord.Id == part.Id).Select(r => r.MedewerkerPartRecord.Id).ToList();

            if (oldCast.Count() == 1 && viewModel.Medewerkers == null)
            {
                _schoolMedewerkerRecordRepository.Delete(_schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldCast.First() && r.SchoolPartRecord.Id == part.Id));
            }

            else
            {

                foreach (var oldMedewerkerId in oldCast.Except(viewModel.Medewerkers))
                {
                    var medewerkertodelete = _schoolMedewerkerRecordRepository.Get(r => r.MedewerkerPartRecord.Id == oldMedewerkerId && r.SchoolPartRecord.Id == part.Id);
                    _schoolMedewerkerRecordRepository.Delete(medewerkertodelete);
                }
                foreach (var newMedewerkerId in viewModel.Medewerkers.Except(oldCast))
                {
                    var medewerker = _medewerkerPartRecordRepository.Get(newMedewerkerId);
                    _schoolMedewerkerRecordRepository.Create(new SchoolMedewerkerRecord { MedewerkerPartRecord = medewerker, SchoolPartRecord = part.Record });
                }
            }
        }

 

I also had to update my SchoolHandler on OnRemoved beceause i had to remove the cascading (I hard delete the contentpart if a school is deleted) and now added logic for deleting the records from the joint table when a school is deleted that had employees connected:

public class SchoolHandler : ContentHandler
    {

        private readonly IRepository _schoolMedewerkerRecordRepository;
        public SchoolHandler(IRepository schoolPartRepository, IRepository schoolMedewerkerRecordRepository)
        {
            _schoolMedewerkerRecordRepository = schoolMedewerkerRecordRepository;
            Filters.Add(StorageFilter.For(schoolPartRepository));
            OnRemoved((context, part) =>
            {
                schoolPartRepository.Delete(part.Record);
                var cast = _schoolMedewerkerRecordRepository.Fetch(sm => sm.SchoolPartRecord.Id == part.Id);
                
                foreach (var rectodeleteId in cast)
                {
                    _schoolMedewerkerRecordRepository.Delete(rectodeleteId);
                }
                
            });
        }
    }
Developer
Dec 18, 2012 at 3:49 PM

Glad to see you got it working.