Database records are not deleted when removing content items

Topics: Writing modules
Jul 2, 2012 at 12:42 PM

Hey,

I'm having an issue when removing a content item, the content part records are not deleted from the database!

I've setup a many to many relationship between 2 content types (schools and employees) where I add content items (employees) to content item school via a dropdown list.

When I delete an employee the employee is not shown anymore in the content items but off course as it is not really deleted from the database it still shows up when I populate my dropdown.

How can I have Orchard to hard delete the records?

Borrie

Developer
Jul 2, 2012 at 9:16 PM

Maybe inject an IRepository of the entities you whish to remove, and invoke the Remove method. The ContentManager will only do a "soft" delete.

Jul 2, 2012 at 9:21 PM

Skywalker,

 

Tx, do you now where in the code I can do that? (Or do you have a code example?) For content types I don't have a delete section, I just have the editor and update...

Borrie

Coordinator
Jul 3, 2012 at 3:18 AM

More importantly, that dropdown should query the content manager specifying VersionOptions.Published. This will filter out the deleted records. You are probably making a query directly on the table for the record from what you're describing.

Jul 3, 2012 at 9:51 AM

Bertrand, that would be an option too, do you have a code snippet or an example on how I can do that? I'm searching but can't seem to find it.

I'm filling my dropdown like you said via a repository directly on that table:

 AllEmployees = _employeePartRecordRepository.Table.OrderBy(m => m.Lastname).ToList()

How can I link it to VersionOptions.Published?

Borrie

Jul 3, 2012 at 1:59 PM

Ok, found it myself:

AllEmployees= _contentManager.Query<EmployeePart>().ForVersion(VersionOptions.Published).List()

It works! But still wondering how to hard delete them :)

Borrie

Jul 3, 2012 at 2:55 PM

Here is some example code that I used to trim up and hard delete my taxonomy Terms...

public void trimArticleTaxonomy()
        {
            TaxonomyPart articleTaxo = _taxo.GetTaxonomyByName("ArticleTaxonomy");
            List<string> taxoParents = new List<string>();
            bool hasChild = true;
            int pipe_index = 0;
            string parent = "";
            string child = "";

            // ---------------- //
            // TRIM CHILD NODES //
            // ---------------- //
            foreach (category defcat in brafton.CategoryDefinitions)
            {
                // check for pipe '|' character => has child category
                if (defcat.name.Contains('|'))
                {
                    pipe_index = defcat.name.IndexOf('|');
                    parent = defcat.name.Substring(0, pipe_index);
                    child = defcat.name.Substring(pipe_index + 1, defcat.name.Length - (pipe_index + 1));
                    hasChild = true;
                }
                else // single category is the parent
                {
                    parent = defcat.name;
                    hasChild = false;
                }

                // save parent so can check after all child nodes have been trimmed
                if (!taxoParents.Contains(parent))
                    taxoParents.Add(parent);

                // check if child has existing associations
                // children nodes may not be unique across entire Taxonomy
                // so must check the one tied to this parent
                if (hasChild)
                {
                    TermPart parent_node = _taxo.GetTermByName(articleTaxo.Id, parent);
                    TermPart child_node = _taxo.GetChildren(parent_node)
                                            .Where(x => x.Name == child).SingleOrDefault();
                    if (_repository_termcontentitem
                        .Fetch(x => x.TermRecord.Id == child_node.Id).Count() == 0)
                    {
                        // --------------------------------------------- //
                        // HARD DELETE CHILD NODE NO ASSOCIATED ARTICLES //
                        // --------------------------------------------- //
                        // if after the newest articles have been loaded //
                        // a child node still does not have associated   //
                        // articles, it should be deleted from the tree  //
                        // --------------------------------------------- //
                        _repository_termpart.Delete(child_node.Record);
                        _repository_termpart.Flush();

                        _repository_autoroute.Delete(child_node.As<AutoroutePart>().Record);
                        _repository_autoroute.Flush();
                    }
                }
            }

            // ----------------- //
            // TRIM PARENT NODES //
            // ----------------- //
            foreach (string rent in taxoParents)
            {
                TermPart parent_node = _taxo.GetTermByName(articleTaxo.Id, rent);

                // check if parent has any article associations
                if (_repository_termcontentitem
                    .Fetch(x => x.TermRecord.Id == parent_node.Id).Count() == 0)
                {
                    // --------------------------------------------- //
                    // HARD DELETE PARENTS IF NO ASSOCIATED ARTICLES //
                    // --------------------------------------------- //
                    // child nodes have been stripped above so if a  //
                    // a single parent node still exists & it does   //
                    // not have associated articles, delete it       //
                    // --------------------------------------------- //
                    _repository_termpart.Delete(parent_node.Record);
                    _repository_termpart.Flush();

                    _repository_autoroute.Delete(parent_node.As<AutoroutePart>().Record);
                    _repository_autoroute.Flush();
                }
            }
        }

Jul 3, 2012 at 3:01 PM

or something like this...

// hard delete all ContentItemRecords (recognizable non-part association) that are of specified ContentType
                iSession = _session.For(typeof(ContentItemRecord));
                sqlcmd = "DELETE FROM " + typeof(ContentItemRecord).FullName +
                         " WHERE ContentType_id = " + articletype_id.ToString();
                qry = iSession.CreateQuery(sqlcmd);
                qry.ExecuteUpdate();
                iSession.Flush();
Jul 3, 2012 at 3:09 PM

afassett,

Thanks for the code, I'll try it out!

Borrie

Jul 3, 2012 at 3:57 PM

Bertrand,

I think the VersionOptions.Published is not sufficient, isn't there another parameter to check if the item has been deleted?

Borrie

Coordinator
Jul 4, 2012 at 5:29 AM

You mean that the content manager is returning you deleted items? That should never happen. If you have a repro, please file a bug.

Jul 4, 2012 at 9:51 AM

Betrand,

No that is not the case, my problem is the following:

I have content type school, this has a one to many relation with cities.

Editors of the site can add, edit and remove cities (non-content data)

So when a city is deleted I first check if there are schools still related to that city and I want to return a list of those schools so editors can first change all the cities on those schools. If a city has no more related schools the city can be deleted.

But here's the problem, If a school has not been published yet and I do a search on VersionOptions.Published the city will get deleted and when I return to my school contents I get an error that the non-published schools have a link to a city that doesn't exist.

So how can i return all schools except the ones deleted?

I know that this city example may seem a bit weird but I have other relations too in my module.

Borrie

Coordinator
Jul 5, 2012 at 6:04 AM

There are other values under VersionOptions. Did you check them out? AllVersions and Latest come to mind.

Jul 5, 2012 at 8:16 AM

Betrand,

Yes I've checked them out but none of them give me the deleted ones, this is really a big problem for me, it's weird that nowhere there is a deleted flag?

Borrie

Coordinator
Jul 5, 2012 at 9:01 AM

Now you got me utterly confused: why would you want the deleted ones?

Jul 5, 2012 at 9:49 AM

Bertrand,

For this:

I have content type school, this has a one to many relation with cities.

Editors of the site can add, edit and remove cities (non-content data)

So when a city is deleted I first check if there are schools still related to that city and I want to return a list of those schools so editors can first change all the cities on those schools. If a city has no more related schools the city can be deleted.

But here's the problem, If a school has not been published yet and I do a search on VersionOptions.Published the city will get deleted and when I return to my school contents I get an error that the non-published schools have a link to a city that doesn't exist.

So how can i return all schools except the ones deleted?

I want to use this as some kind of protection that a City cannot be deleted before the related schools are deleted or their city is changed.

As Orchard doesn't really delete content types but keeps them in the database this is a real problem for me + the VersionOptions.Published only returns Published schools, if a school is not published yet and you delete a related city It will give an error that it can't find the related record.

How can I solve this or am I missing something here?

Developer
Jul 5, 2012 at 10:24 PM
Edited Jul 5, 2012 at 10:25 PM

Perhaps you should have a look at implementing content handlers and implement the Removing method for each content part you have. This way, when a user removes a City, you can select all Schools that use this City and set the CityId to null.

Jul 6, 2012 at 9:57 AM
Edited Jul 6, 2012 at 10:58 AM

Skywalker,

That is also an option I was thinking about to add a default value when deleting a city but that would not be clean + then I lose the control of checking wether the city is still related somewhere (if someone accidently removes a city)

I still find it really weird that you cannot check wether a content part is removed or not, how does the system know not to show content parts in the backend that have been removed but it still shows unpublished parts, the key to the answer must be somewhere there no?

Borrie

*Edit

Skywalker,

I'm running into serious issues here with the delete not working properly.

When I created the project first I created schools and employees as non content data. Everything worked fine (also all the delete functions) but I couldn't add a picture to my employees so I switched over to creating them as content types so I could also benefit from the code already being in place.

Now the schools and employees have a many to many relation. This means when I delete an employee it should delete the employee also from the junction-table. As orchard doesn't realy delete anything from the database this is a big problem. When I delete an employee that is connected to a school and when I try to edit that school i get off course an error that the employee was not found and the site crashes.

How can I hard delete content types? I'm really stuck here...

This is for instance my employee model: (I have the [CascadeAllDeleteOrphan] on)

 

public class EmployeePartRecord : ContentPartRecord
    {
       
        public EmployeePartRecord()
        {
           EmployeeSchools = new List();
        }
       
        public virtual string LastName { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string EmailAddres { get; set; }
        public virtual string Telnr { get; set; }
        public virtual FunctionRecord FunctionRecord { get; set; }

        [CascadeAllDeleteOrphan]
        public virtual IList EmployeeSchools{ get; set; }
    }
}
Jul 10, 2012 at 9:56 AM
Edited Jul 10, 2012 at 4:30 PM

OMG!!!

I've found it! :)

Ok, I'll explain it to you noobs like me out there :)

First you have to reference your module into Orchard.core (right click, browse to bin folder of your module and add the dll)

Goto Orchard.Core.Contents.Controllers.AdminController

Add: using YourmoduleName.Models;

Add your repositories, for example:

 

public class AdminController : Controller, IUpdateModel {
        private readonly IContentManager _contentManager;
        private readonly IContentDefinitionManager _contentDefinitionManager;
        private readonly ITransactionManager _transactionManager;
        private readonly ISiteService _siteService;
        private readonly IRepository _schoolPartRecordRepository;
        private readonly IRepository _employeePartRecordRepository;

        public AdminController(
            IOrchardServices orchardServices,
            IContentManager contentManager,
            IContentDefinitionManager contentDefinitionManager,
            ITransactionManager transactionManager,
            ISiteService siteService,
            IShapeFactory shapeFactory,
            IRepository schoolPartRecordRepository,
            IRepository employeePartRecordRepository)
        {
            Services = orchardServices;
            _contentManager = contentManager;
            _contentDefinitionManager = contentDefinitionManager;
            _transactionManager = transactionManager;
            _siteService = siteService;
            T = NullLocalizer.Instance;
            Logger = NullLogger.Instance;
            Shape = shapeFactory;
            _schoolPartRecordRepository = schoolPartRecordRepository;
            _employeePartRecordRepository = employeePartRecordRepository;
        }

Then in the remove section I've added this code:

 [HttpPost]
        public ActionResult Remove(int id, string returnUrl) {
            var contentItem = _contentManager.Get(id, VersionOptions.Latest);

            if (!Services.Authorizer.Authorize(Permissions.DeleteContent, contentItem, T("Couldn't remove content")))
                return new HttpUnauthorizedResult();

            if (contentItem != null) {
                _contentManager.Remove(contentItem);
                if (contentItem.ContentType.ToString() == "School" ){
                    var school = _schoolPartRecordRepository.Get(id);
                    _schoolPartRecordRepository.Delete(school);
                }

                if (contentItem.ContentType.ToString() == "Employee")
                {
                    varemployee = _employeePartRecordRepository.Get(id);
                    _employeePartRecordRepository.Delete(employee);
                }

                Services.Notifier.Information(string.IsNullOrWhiteSpace(contentItem.TypeDefinition.DisplayName)
                    ? T("That content has been removed.")
                    : T("That {0} has been removed.", contentItem.TypeDefinition.DisplayName));
            }

            return this.RedirectLocal(returnUrl, () => RedirectToAction("List"));
        }
So now the records are deleted from the database and also from the junction table!

I'm happy! :)

Borrie