I want to remove all documents from a database except 1, the configuration document. I have the following ssjs code:
var dc:NotesDocumentCollection = database.getAllDocuments();
var vw:NotesView = database.getView("configuration");
var doc:NotesDocument = vw.getFirstDocument();
if (dc.getCount() > 0){
if (doc != null){
dc.deleteDocument(doc);
}
dc.removeAll(false);
}
however when I run the script I get an error on command dc.deleteDocument(doc);
What am I doing incorrect?
Use
dc.subtract(doc);
instead of deleteDocument(). This is the recommended way to remove a document from a collection.
They can be a pain indeed, but they are not buggy, IMHO. One just has to know that they behave somewhat differently, especially when using replication, for replication conflicts never merge. That shouldn't stop you from using them, the advantages largely outweigh the inconveniences, in my experiences...
Related
I am working on microservices in .Net Core with the domain driven design. The infrastructure layer has EF Core DbContext to access the database and in my repositories, I have async methods to retrieve data.
Because Include/ThenInclude does not support filtering (at least not up to Ef Core 2.1), I have tried all the possible approaches I found when googling on how to replace Include. I watched the Pluralsight videos about Ef Core too and when I saw the Explicit Loading option I was really happy due to its ability to filter related objects, but when I rewrote one of the methods into Explicit version, the query that ran for a couple of milliseconds went up to a couple of minutes!
In my entity configurations I set up all the navigations and foreign keys, but I am not sure whether explicit loading requires any additional setup or not? Before recommending to use global filters, please note that the Where clause is usually longer, so the below example is just a shortened version of the actual filtering!
This is how my methods look like (TransferService serves as the aggregate, TransferServiceDetail and any other classes are just entities within the TransferService domain):
public async Task<IEnumerable<TransferService>> GetAllAsync(
TransferServiceFilter transferServiceFilter)
{
int? pageIndex = null;
int? itemsPerPage = null;
IEnumerable<TransferService> filteredList = DBContext.TransferServices.Where(
ts => !ts.IsDeleted); //This one itself is quick.
//This is just our filtering, it does not affect performance.
if (transferServiceFilter != null)
{
pageIndex = transferServiceFilter.PageIndex;
itemsPerPage = transferServiceFilter.ItemsPerPage;
filteredList = filteredList.Where(f =>
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(transferServiceFilter.TransferDestinationId == null ||
f.TransferDestinationId == transferServiceFilter.TransferDestinationId) &&
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(string.IsNullOrEmpty(transferServiceFilter.TransportHubRef) ||
f.NormalizeReference(f.TransportHubRef) ==
f.NormalizeReference(transferServiceFilter.TransportHubRef)));
}
//This is just for paging and again, this is quick.
return await FilterList(filteredList.AsQueryable(), pageIndex, itemsPerPage);
}
public async Task<IEnumerable<TransferService>> GetAllWithServiceDetailsAsync(
TransferServiceFilter transferServiceFilter)
{
IEnumerable<TransferService> returnList = await GetAllAsync(
transferServiceFilter);
//This might be the problem as I need to iterate through my TransferServices
//to be able to load all TransferServiceDetails that belong to each individual
//Service.
foreach (TransferService service in returnList)
{
await DBContext.Entry<TransferService>(service)
.Collection(ts => ts.TransferServiceDetails.Where(
tsd => !tsd.IsDeleted)).LoadAsync();
}
return returnList;
}
In my repository I have other methods as well, similarly referring to a previous GetAllXY... method (TransferServiceDetails have Rates, Rates have Periods, etc...).
My idea was to simply call GetAllAsync when I only need TransferService data (and alone this method is lightning quick), or call GetAllWithServiceDetailsAsync when I also need the Details of the selected Services, etc, but the lower I go in this parent-child hierarchy, the slower the execution becomes and I am talking about minutes, not just a couple of extra milliseconds, or in worst case seconds.
So my question again: is there any additional setting that I might have missed from the entity configurations that explicit loading requires, or simply my queries are incorrect? Or maybe explicit loading is only good when there is only one TransferService as a parent instead of a list of TransferServices (50-100 in my case) and also there are only just a few children related entities (in my case I usually have 5-10 Details, each Detail has 2-3 Rates, each Rate has exactly 1 Period, etc...)?
I guess your filtering can't be converted to SQL Where and all filtering happens client-side (EF loads ALL TransferServices entities into memory, filters in-memory and drops mismatched).
I may check this by enabling detailed (debug) logging - EF will dump SQLs into log.
After you confirm, your should make improvements:
First, put ifs out of Where. Instead of:
filteredList = filteredList.Where(f => transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
use
if (transferServiceFilter.TransferSupplierId != null)
{
filteredList = filteredList.Where(f => f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
}
Second, you should re-think NormalizeReference. This can't be executed server-side, because SQL server doesn't know about this implementation. You should pre-normalize TransportHubRef, save it in DB (say, NormalizedTransportHubRef) and use Where with simple equality.
(Also, don't forget about indexes).
I'm trying to use a background task to gather Likes/Comments from the Facebook Graph APi and use that to drive our blog's trending.
Here the trendingModels have already been populated and are being used to fill in the TrendingParts.GraphId and TrendingParts.TrendingValue.
I'm not getting any exceptions and the properties on TrendingPart point to the fields in the TrendingPartRecord.
Yet nothing persists to the database, any ideas why?
_orchardsServices is IOrchardServices
var articleParts = _orchardService.ContentManager.GetMany<TrendingPart>(
trendingModels.Select(r => r.OrchardId).ToList(),
VersionOptions.Published,
QueryHints.Empty);
// Cycle through the records and update them from the matching model
foreach (var articlePart in articleParts)
{
ArticleTrendingModel trendingModel = trendingModels.Where(r => r.OrchardId == articlePart.Id).FirstOrDefault();
if(trendingModel != null)
{
// Not persisting to the database, WHY?
// What's missing?
// If I'm understanding things properly nHibernate should push this to the db autoMagically.
articlePart.GraphId = trendingModel.GraphId;
articlePart.TrendingValue = trendingModel.TrendingValue;
}
}
Edit:
It's probably worth noting that I can update and publish the fields on the TrendingPart in the admin panel but the saved changes don't appear in the MyModule_TrendingPartRecord table.
The solution was to change my Service to a transient dependency using ITransientDependency.
The service was holding a reference to the PartRecords array and because it was treated as a Singleton it never disposed and the push to the database was never made.
I am importing products from a product feed. A product has a designer and a category and I made them separate tables (Parse classes).
The problem now is that I need to check if a category or designer already exists so I don't need to create it.
Currently I am not checking it, causing many duplicates in my DB. If I checked in DB, it would be asynchronous but I actually need it to be somehow sequential(serialized).. Any ideas?
for (var i=1;i<productsFromFeed.length;i++){
productId = parseInt(productsFromFeed[i][1]);
if (productId > lastProduct.get("productId")){
console.log("Product is new: " + productId);
var Designer = Parse.Object.extend("Designer");
var Product = Parse.Object.extend("Product");
var Category = Parse.Object.extend("Category");
var designer = new Designer();
designer.set("name", productsFromFeed[i][4]);
designer.set("designerId", parseInt(productsFromFeed[i][5]));
var category = new Category();
category.set("name", productsFromFeed[i][6]);
category.set("categoryId", parseInt(productsFromFeed[i][7]));
var newProduct = new Product();
newProduct.set("productName", productsFromFeed[i][0]);
newProduct.set("productId", parseInt(productsFromFeed[i][1]));
newProduct.set("designer", designer);
newProduct.set("category", category);
objects.push(newProduct);
} else {
console.log("Product already exists: " + productId);
}
}
It doesn't need to be serial, you just need to deal with the asynchronous nature of what you're trying to do - using promises.
You could run a query to find everything in advance and locally cache them, that's only useful if you're doing a bulk import, your code does look like a small bulk import but it isn't clear if this will be an efficient approach.
In your case just run your queries and use promises to chain each logical stage together. Note that for your loop you can put the resulting promises into an array and wait till they're all complete.
I'm trying to delete selected doc from viewPanel1. The view is categorized ( can be > 1 category ) and is listing documents from 2 different datasources, let say: Cdoc and Pdoc. These docs. are linked by a common field.
my scenario: If the users select a Cdoc => the delete action will take place to the respective Cdoc but also for the all Pdoc being in the same category. If the user selects a Pdoc => delete just the Pdoc. Also, I would like to add some confirmation text with some information ( Value fields ) from the selected documents.
I tried the following
var viewPanel=getComponent("viewPanel1");
var docIDArray=viewPanel.getSelectedIds();
for(i=0;i < docIDArray.length;i++){
var docId=docIDArray[i];
var doc=database.getDocumentByID(docId);
var formName = (doc == null)? null : doc.getItemValueString("Form");
if( formName =="fmPersContact" ){
.....
} // in this case, it works OK.
else if ( formName =="fmCompanie" ){ // here if I selected > 1 Cdoc, it deletes just one Cdoc + the respective PDocs.
var doc:NotesDocument = null;
doc=database.getDocumentByID(docId);
var ky:java.util.Vector = new java.util.Vector();
ky.add(doc.getItemValueString("txt_NumeCompanie"));
... // delete method
}
Could you tell me what I did wrong and what am I missing in the above code? thanks for your time!
The first thing you want to do is confirm. Unlike Lotusscript, you cannot use a function in the middle of a script to open the confirm dialog and get the answer. To do this, I recommend using the confirm simple action before going into an execute script simple action.
<xp:button
value="delete"
id="button1"
>
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:actionGroup>
<xp:confirm message="Are you certain?"></xp:confirm>
<xp:executeScript
script="#{javascript:doSomething();}"
>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action></xp:eventHandler></xp:button>
EDIT
In the past, I have also used built my own dialogs with the extension library, filled the text in with SSJS and then called the doWhatever() or close() from the dialog itself. This is not the best solution as it requires an update from the server to get the string. The best solution would be, as Paul Withers says, to use CSJS to perform the confirmation. I have yet to do this though.
/EDIT
For your delete function, I recommend getting the document you want to delete, then tell whether it is a P- or C- doc, either by the form name or whatever mechanism you use, and then either delete the single document or by getting a documentcollection from the view by using getAllDocumentsBykey(), then iterating through them all, deleting them one by one.
var ky:java.util.Vector = new java.util.Vector();
ky.add("MainCat");
ky.add("subCat");
ky.add("subCat2");
var vw:NotesView = database.getView("vw_myView");
var docs:NotesDocumentCollection = vw.getAllDocumentsByKey(ky);
//... delete stuff...
//dont forget to recycle
Post Question Edit
I recommend the following to get the form name:
var getSelectedDoc = function(){
var vwPnl = getComponent("viewpanel");
var ids = vwPnl.getSelectedIds();
var id = null;
var doc:NotesDocument = null;
if(ids.length > 0){ //could use for loop var i = 0; i < ids.length;i++
id = ids[0]; //could pack all ids into java.util.ArrayList and return that list to work on further
//be warned that if the user selects a parent doc and those automatically deleted by it that you need a mechanism to check if the document was already deleted!
}
if(id != null){
doc = database.getDocumentByID(id);
}
return doc;
}
var doc = getSelectedDoc();
var formName = (doc == null)? null : doc.getItemValueString("form");
if(PDOC_FORM_NAME.equalsIgnoreCase(formName)){
deleteFunctionComplete(doc);
} else if (CDOC_FORM_NAME.equalsIgnoreCase(formName)){
deleteFunctionTwo(doc);
} else {
// uh-oh
}
this also allows you to have the document in case you want to delete it right away.
Edits for comments
If Cdoc should delete more than one document, then yes. You should be using the getAllDocumentsBykey keeping in mind that the view needs to be built for it. By that I mean if you have a view with one single category, there is no issue, just plug in the string and you are fine. If you have a view with three categories, you cannot feed a vector into the getalldocs function with only two values, it must be all three. So, you want to delete all for "mycomp" with the underlying pdocs "Greg" "Sally "Bob", just use alldocsbykey("mycomp"), if the view looks like:
mycomp
---Greg
---Sally
---Bob
but if the view looks like
Poland
---mycomp
------Greg
------Sally
------Bob
then the a vector with poland and mycomp must be used. "poland" does not get the correct documents. --just an fyi and pitfall that is sometimes had.
Edit after further question clarification
I prefer this loop style to remove docs
var doc_temp:NotesDocument = null;
var doc_toDelete:NotesDocument = null;
var coll_docs:NotesDocumentCollection = ...; //get document collection
var doc_nextDoc = coll_docs.getFirstDocument();
while(doc_nextDoc != null){
doc_temp = doc_nextDoc; //set document to delete
doc_nextDoc = coll_docs.getNextDocument(doc_nextDoc); // set next document before deletion
try{
doc_temp.remove(true);//lots of errors can happen here, such as ACL settings
} catch(e) {
//handle, or just break
} finally{
if(doc_temp != null) try{doc_temp.recycle()} catch(e){}// try to recycle, could also cause errors
doc_temp = null;// for the sense of completeness
}
}
Even further edit based on the question edits
of course you are only deleting one Pdoc, the way you have that set up, you are only ever returning one document. You could expand the getSelectedDoc() to put all selected documents into an java.util.ArrayList or something, and then use that arraylist to delete more than one at a time, but that could be dangerous depending on what you do because NotesDocuments are not serialisable. In that case, I recommend using the same code that you use for getSelected doc, use a for loop to get the document IDs, get the document, if the document is not null, then delete.
apropos getAllDocumentsByKey(with a vector)
The way this is currently set up, no Vector is necessary.
If you have a view with a category and sub category and you want to get all the documents in that sub category, then you must use a vector to get at it. If you include a simple string or a vector with only one value, then the documents in the sub category will not be returned. The vector can be thought of as "cat1", "subcat", "furtherSubCat"
Furthermore, there is no check here to see if the string returned from the document is empty. This should be done. There is also no check to see if the DocumentCollection is empty. This should also be done. My expectation is that there is an issue retrieving the collection based on above mentioned reasons.
I want to get all conflict documents from a Notes database. So far, i've got this:
Domino.NotesSession notesSession;
Domino.NotesDatabase notesDatabase = this.OpenDatabase(out notesSession);
Domino.NotesDateTime dateTime = notesSession.CreateDateTime(String.Empty);
Domino.NotesDocumentCollection results =
notesDatabase.Search(this.SearchString, dateTime, 0);
It works with, for example:
searchString = "#Contains(ShortName;\"Bob\")";
How can I do the equivalent for conflict documents?
Try this:
searchString = "#IsAvailable($Conflict)";
There is a field on a document that flags any Notes document as a conflict called "$Conflict". If it's present on the document, then you know it's a conflict, (like Carlos is eluding to).
You can create a view in the database that has the formula.
Select #isAvailable("$Conflict")
and then loop through all documents in the view. It looks like you're doing it in Java so I think it would look like this
import lotus.domino.*;
import java.util.*;
//.....
//.....
Session s = NotesFactory.createSession();
Database db = s.getDatabase("server", "filename");
View vw = db.getView("viewname");
Document doc = null;
doc = vw.getFirstDocument();
while (doc != null) {
// do what you want in here.
doc = vw.getNextDocument(doc);
}
You'll need to make sure you have added the Domino jars to your project. This is a good reference for setting up the eclipse IDE for Domino java development.
PS. You can also modify the design of the database to minimise replication conflicts. But I won't bore you here with the details. Post a comment if you would like to know and ill provide instructions on this thread.