I’m attempting to convert some code from using ThinkGeo Map Suite Services Edition 6.0 to version 9.0. In my code, after I delete a record from the Shapefile, I
1. close the Shapefile,
2. call ShapeFileFeatureLayer.Rebuild(FullPathToShapefile)
3. Open it back up again
4. call BuildRecordIdColumn(RowIDColumnName, BuildRecordIdMode.Rebuild) on the layer
This works perfectly in version 6.0, but when I attempt it with version 9.0, step #4 throws the exception
System.InvalidOperationException: You are operating a Record which has been removed
And sure enough, if I set a breakpoint on step #2 and wait a couple minutes (so my system clock is now different than the last modified time on the files making up the Shapefile) before allowing step #2 to execute, I see that the last modified time of all the files that make up the Shapefile remain unchanged and therefore I must conclude that step #2 didn’t actually do anything. And, as I would expect if step #2 was doing nothing instead of actually rebuilding the Shapefile as I think it is supposed to, if I throw the .dbf file into Notepad I can clearly see the record which should have been deleted is still sitting there with an * in front of it tagging it as a record which should be treated as deleted.
Is ShapeFileFeatureLayer.Rebuild broken in 9.0 and/or is there now an alternative means by which I should be eliminating deleted rows from Shapefiles instead of using Rebuild?
Thanks,
John
ShapeFileFeatureLayer.Rebuild not removing deleted records
Hi John,
If your target is delete record from shape file then update it, I think this function as below should works better:
ShapeFileFeatureSource featureSource = new ShapeFileFeatureSource(“path”);
featureSource.BeginTransaction();
featureSource.DeleteFeature(“targetFeature”);
featureSource.CommitTransaction();
ShapeFileFeatureSource.BuildIndexFile(“path”, BuildIndexMode.Rebuild);
And which won’t need to close the shape file.
Please let me know whether that’s helpful for your scenario.
Regards,
Don
It doesn’t appear I can call BuildIndexFile without closing the layer first. If I don’t close the layer I get the following exception. I’ll experiment a bit more tomorrow to see if I do close the layer whether BuildIndexFile succeeds and gets rid of the deleted records from the Shapefile.
System.IO.IOException: The process cannot access the file ‘<path removed>\MAPFIELD.idx’ because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
at System.IO.File.Delete(String path)
at ThinkGeo.MapSuite.Core.ShapeFileFeatureSource.NxY=(String pathFileName)
at ThinkGeo.MapSuite.Core.ShapeFileFeatureSource.OBY=(String sourcePathFileName, String targetPathFileName)
at ThinkGeo.MapSuite.Core.ShapeFileFeatureSource.BuildIndexFile(String shapePathFilename, String indexPathFilename, Projection projection, String columnName, String regularExpression, BuildIndexMode buildIndexMode, Encoding encoding)
at ThinkGeo.MapSuite.Core.ShapeFileFeatureSource.BuildIndexFile(String shapePathFilename, String indexPathFilename, Projection projection, String columnName, String regularExpression, BuildIndexMode buildIndexMode)
at ThinkGeo.MapSuite.Core.ShapeFileFeatureSource.BuildIndexFile(String shapePathFilename, BuildIndexMode buildIndexMode)
OK, if I close the layer before calling BuildIndexFile, then it (rebuilding the index) appears to work instead of throwing an exception. My issue however is that BuildIndexFile does nothing about removing the deleted record from the Shapefile. The deleted record is still sitting in the .DBF file with an * in front of it marking it as deleted. So BuildIndexFile appears to work, it just doesn’t do the same thing as Rebuild so is not a suitable replacement for Rebuild. It merely modifies the index stored in the .ids and .idx files of the Shapefile and does nothing with the .dbf, .shp, or .shx files.
I wish I could concisely explain why it is that we need to eliminate the deleted records from the Shapefile, but it’s been so long (years) since we tackled this problem that I can’t clearly remember. I think it comes down to ShapeFileFeatureLayer using the row number of the record as the Feature ID and therefore if there are deleted records in the Shapefile that ID not actually matching up correctly. Something like not being able to fetch the last record (by calling GetFeatureByID) or getting an exception when attempting to fetch the record immediately after the one that was deleted, or perhaps adding a record and not knowing what ID that record is given if there are possibly some deleted records still hanging around. I think it came down to us receiving exceptions when attempting to fetch specific rows from the Shapefile by id and our solution was therefore to always call Rebuild and BuildRecordIdColumn after every delete from a Shapefile so that Feature.Ids match up with row numbers and GetFeatureByID always works and always returns us the row we expect it to.
Perhaps the description from that Rebuild method from your Wiki gives a better explanation than I can of what Rebuild should be doing and therefore why we’re calling it after record deletes (although I think that should say “SHP and SHX” as opposed to “SPX and SHX”).
From the Wiki: This method rebuilds the SHP, SHX, DBF, IDX and IDS files for the given shape file. When we do editing we have optimized the updates so that we do not need to rebuild the entire shape file. This leads to the shape file being out of order which may cause it not to open in other tools. One optimization is if you update a record instead of rebuilding a new shape file we mark the old record as null and add the edited record at the end of the shape file. This greatly increases the speed of committing shape file changes but will over time unorder the shape file. In addition we do a delete the DBF file will simply mark the record deleted and not compact the space. Rebuilding the shape file will correctly order the SPX and SHX along with compacting the DBF file and rebuild any index with the same any of the shape file if it exists.
Hi John,
Thanks for your detail description, that’s helpful for let me understand your question.
As below is my test code and data, it looks the Rebuild works well follow the latest code, could you view it and help me modify it so I can reproduce that?
Regards,
Don
string
testdata = @
“D:\temp\LALpt60.shp”
;
ShapeFileFeatureSource featureSource =
new
ShapeFileFeatureSource(testdata, ShapeFileReadWriteMode.ReadWrite);
featureSource.Open();
Collection<Feature> fs = featureSource.GetAllFeatures(ReturningColumnsType.AllColumns);
Feature f = featureSource.GetFeatureById(
“5”
, ReturningColumnsType.AllColumns);
Debug.WriteLine(f.ColumnValues[
“NAME”
]);
featureSource.BeginTransaction();
featureSource.DeleteFeature(
“5”
);
featureSource.CommitTransaction();
featureSource.Close();
ShapeFileFeatureSource.Rebuild(testdata, ShapeFileSortingMode.Default, 4326);
featureSource.Open();
Feature f2 = featureSource.GetFeatureById(
“5”
, ReturningColumnsType.AllColumns);
Debug.WriteLine(f2.ColumnValues[
“NAME”
]);
13157.zip (1.54 KB)
Thank you very much. Using Rebuild like this should get me what I’m after. I have a couple questions however based on what I’m seeing Rebuild do.
First off, if I replace the call to ShapeFileFeatureSource.Rebuild(testdata, ShapeFileSortingMode.Default, 4326);
in your sample code with this one ShapeFileFeatureSource.Rebuild(testdata);
It doesn’t appear to do anything. The deleted record still exists within the .DBF file. Why is this means of calling Rebuild no longer doing anything? I’m not seeing any compiler warnings about it being depricated nor any errors from using it, it just doesn’t seem to be doing anything anymore.
Second question, if I replace the call to ShapeFileFeatureSource.Rebuild(testdata, ShapeFileSortingMode.Default, 4326);
with ShapeFileFeatureSource.Rebuild(testdata, ShapeFileSortingMode.None, 4326);
the ordering of the records in the resulting Shapefile isn’t quite what I would expect. ShapeFileSortingMode.Default appears to leave the order alone in this example and therefore the records which started as 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 after deleting 5 and doing the Rebuild with ShapeFileSortingMode.Default I end up with the records in order 1, 2, 3, 4, 6, 7, 8, 9, 10. Doing the same but specifying ShapeFileSortingMode.None I end up with records in order 1, 10, 2, 3, 4, 6, 7, 8, 9; that seems rather strange as by specifying None I would expect that to mean not to reorder them, but maybe “None” means something more akin to “Do whatever you want” and “Default” means leave the order unchanged. So I used “Default” in my own app, added two records to my Shapefile, which would have added them to the end, then deleted the second to last one I had added and checked the results after having called Rebuild. To my surprise my recently added record was now about in the middle of my Shapefile (record number 25 out of 46). So it appears neither “None” nor “Default” actually mean leave the order of records in the DBF file unchanged. So, I guess my two part question is what do “ShapeFileSortingMode.None” and “ShapeFileSortingMode.Default” actually mean and is there an option that can be specified to tell it to leave the order within the .DBF file unchanged (but still compact the Shapefile)?
Thanks for any insight you can provide.
John
Hi John,
1. If you directly call Rebuild(testdata), it equal call like this: Rebuild(shapePathFilename, ShapeFileSortingMode.Default, 0), and it won’t work well. In fact I think the default srid should use 4326, but I am not very sure about why developer set zero here, maybe there is some thinking I hadn’t understood, I will try to figure it out.
2. It looks ShapeFileSortingMode.Default equal ShapeFileSortingMode.GeoHash. For ShapeFileSortingMode.None, it looks it rebuild the order follow the first column, you can see the result as below:
And if your requirement is leave the 5 as empty, then keep 1, 2, 3, 4, 6, 7, 8, 9, 10. I think you cannot call rebuild but just keep the 5 marked as deleted, I hadn’t found another solution till now. If you call that by ShapeFileSortingMode.Default, the id will be reassign and won’t leave an empty seat.
Regards,
Don
Regarding 2: Now that makes sense. Sorted alphabetically by the first column in the table. It would make more sense if the first column were a character column as opposed to a numeric one storing my row number, but at least now I know why the order is the way it is.
I don’t believe I explained very clearly what I perceived to be the behavior in version 6.0 and what I was hoping was possible with 9.0. That being if I have records with row number 1 through 10, and I remove 5, then what I’d like to have left after doing that (and after calling Rebuild and BuildRecordIdColumn) would be the remaining 9 rows all in their same original order and now having row numbers 1 through 9. My reason for wanting this s quite weak; it’s that it used to behave that way (in version 6.0) so I can be sure that if it is still behaving that way then there is no change that could possibly break something elsewhere in my application. I don’t have to retest everything to make sure that there have been no assumptions made regarding row ordering that are now violated by having the Rebuild sort the records differently. Like I said, that’s a weak reason and we can go through the effort of searching for and/or testing for such assumptions if there’s no longer any way built into in 9.0 to instruct Rebuild to behave (regarding row ordering) as it did in version 6.0.
Hi John,
Thanks for your understanding.
It’s a long term from 6.0 to 9.0, I think there are many changes between them. I am sorry I don’t clearly know about each change, or else I could tell you whether the behavior had been modified when you mentioned it.
But if you have any question need our help please feel free to let us know, I will do my best to understand the problem and try to help you find the solution or workaround.
Regards,
Don