Im developing a tool that needs to access to the names.nsf database inside IBM Lotus Notes, and, using the lotus contact ID (Employee ID) (this id will be provided by the user), retrieve the full information of the person (Name, Position, Phone #....)
I found an example at Codeproject.com (http://www.codeproject.com/Articles/18517/Lotus-Notes-Integration-with-Microsoft-NET-Platfor), however it takes around 10 minutes to get the information the way the example does it (the database has more or less 5000 entries), so I'm searching for a faster way of doing it (if I actually use Lotus notes for this it takes about a second!).
Is there a way to accomplish this task without having the user waiting for minutes?
Thought that maybe you can help me out with this one.
The sample you are using goes through the view using
NotesViewEntry viewEntry = notesViewCollection.GetNthEntry( rowCount );
This is (one of) the worst methods to use as it goes for every iteration from the top of the view and iterates through all docs until it reached the nth document.
There are two options:
1) Optimize this code by using
NotesViewEntry viewEntry = notesViewCollection.GetFirstEntry();
and at the end
viewEntry = notesViewCollection.GetNextEntry(viewEntry);
2) (in my humble opinion the better way): Change the code:
- you need a view with the first column sorted by your key => contact ID (Employee ID)
- You can the access the ViewEntry by a code like
LotusNotesView.GetEntryByKey( EmployeeID, true);
If you are lucky the names.nsf is full text indexed. If it's not you could try to ask if it could be full text indexed. When it's indexed you can get the person document quicly like this:
LotusNotesView.FTSearch("[EmployeeID]=1234567", 1);
NotesDocument docPerson = LotusNotesView.GetFirstDocument();
The use of GetNthEntry certainly causes some performance issues. I've taken the relevant code from that site and rewrote it to use the GetFirst/GetNext pattern, which is recommended for all view processing in Lotus Notes.
Note this hasn't been tested, of course. The point is to get the first entry in your collection, check that it is an object, and then process it. At the end of the loop, get the next entry and repeat until you hit null.
NotesViewEntryCollection notesViewCollection = LotusNotesView.AllEntries;
NotesViewEntry viewEntry = notesViewCollection.GetFirstEntry();
while (viewEntry != null)
{
//Get the first document of particular entry.
NotesDocument document = viewEntry.Document;
object documentItems = document.Items;
Array itemArray1 = (System.Array)documentItems;
for( int itemCount=0 ; itemCount< itemArray1.Length; itemCount++ )
{
NotesItem notesItem =
(Domino.NotesItem)itemArray1.GetValue( itemCount );
//compare field value with specific value entered by user
if( notesItem.Text !=null )
{
if( (notesItem.Text.ToUpper()).StartsWith( fieldValue ))
{
Contact contact = new Contact();
for( int icount=0 ; icount< itemArray1.Length; icount++ )
{
NotesItem searchedNotesItem =
(Domino.NotesItem)itemArray1.GetValue( icount );
string FieldName = searchedNotesItem.Name.ToString();
//For FirstName
if( searchedNotesItem.Name == "FirstName" )
contact.FirstName= searchedNotesItem.Text;
//For LastName
if( searchedNotesItem.Name == "LastName" )
contact.LastName = searchedNotesItem.Text;
//For Office Phone Number
if( searchedNotesItem.Name == "OfficePhoneNumber" )
contact.OfficePhoneNumber = searchedNotesItem.Text;
if( searchedNotesItem.Name == "InternetAddress" )
contact.EmailId = searchedNotesItem.Text;
}//end for
contactsList.Add( contact );
break;
}//End if
}
}
//Get the nth entry of the selected view according to the iteration.
NotesViewEntry viewEntry = notesViewCollection.GetNextEntry(viewEntry);
}
Why are you asking the user to provide his Employee ID? You should ask him to provide his Notes username (either FullName or ShortName), or his email address. Any of those can be looked up very quickly in the $Users view in names.nsf, giving you fast access to the document containing all the data that you need.
Note: I'm aware that some companies actually enter their Employee ID into the ShortName field in names.nsf. If that's the case for your organization, then what you should be doing is opening a NotesView object using the NotesDatabase.getView() method, and then use the NotesView.getDocumentByKey() method to get the document for the user. E.g., something like this:
NotesView usersView = namesDb.getView("$Users");
NotesDocument userDoc = usersView.getDocumentByKey(employeeId);
Then just read the data that you want, using userDoc.getItemValue() for each information field that you are interested in. You should only do a loop through the entire userdoc.Items array if you are really trying to capture everything, including a bunch of internal-use values.
Related
I'm trying to build a standalone application that creates a custom report for Encompass360 without needing to put certain fields into the reporting database.
So far I have only found one way to do it, but it is extremely slow. (Much slower than a normal report within encompass when retrieving data outside of the reporting database.) It takes almost 2 minutes to pull the data for 5 loans doing this:
int count = 5;
StringList fields = new StringList();
fields.Add("Fields.317");
fields.Add("Fields.3238");
fields.Add("Fields.313");
fields.Add("Fields.319");
fields.Add("Fields.2");
// lstLoans.Items contains the string location of the loans(i.e. "My Pipeline\Dave#6")
foreach (LoanIdentity loanID in lstLoans.Items)
{
string[] loanIdentifier = loanID.ToString().Split('\\');
Loan loan = Globals.Session.Loans.Folders[loanIdentifier[0]].OpenLoan(loanIdentifier[1]);
bool fundingPlus = true; // if milestone == funding || shipping || suspended || completion;
if (!fundingPlus)
continue;
bool oneIsChecked = false;
LogMilestoneEvents msEvents = loan.Log.MilestoneEvents;
DateTime date;
MilestoneEvent ms = null; // better way to do this probably
if (checkBox4.Checked)
{
ms = msEvents.GetEventForMilestone("Completion");
if (ms.Completed)
{
oneIsChecked = true;
}
}
else if (checkBox3.Checked)
{
ms = msEvents.GetEventForMilestone("Suspended");
if (ms.Completed)
{
oneIsChecked = true;
}
}
else if (checkBox2.Checked)
{
ms = msEvents.GetEventForMilestone("Shipping");
if (ms.Completed)
{
oneIsChecked = true;
}
}
else if (checkBox1.Checked)
{
ms = msEvents.GetEventForMilestone("Funding");
if (ms.Completed)
{
oneIsChecked = true;
}
}
if (!oneIsChecked)
continue;
string LO = loan.Fields["317"].FormattedValue;
string LOid = loan.Fields["3238"].FormattedValue;
string city = loan.Fields["313"].FormattedValue;
string address = loan.Fields["319"].FormattedValue;
string loanAmount = loan.Fields["2"].FormattedValue;
if (loanAmount == "")
{
Console.WriteLine(LO);
continue;
}
int numLoans = 1;
addLoanFieldToListView(LO, numLoans, city, address, loanAmount);
if (--count == 0)
break;
}
}
I haven't been able to figure out how to use any of the pipeline methods to retrieve data outside the reporting database, but when all of the fields I am looking for are in the reporting database, it hardly takes a couple seconds to retrieve the contents of hundreds of loans using these tools:
session.Reports.SelectReportingFieldsForLoans(loanGUIDs, fields);
session.Loans.QueryPipeline(selectedDate, PipelineSortOrder.None);
session.Loans.OpenPipeline(PipelineSortOrder.None);
What would really help me is if somebody provided a simple example for retrieving data outside of the reporting database by using the encompass sdk that doesn't take longer than it ought to for retrieving the data.
Note: I am aware I can add the fields to the reporting database that aren't in it currently, so this is not the answer I am looking for.
Note #2: Encompass360 doesn't have it's own tag, if somebody knows of better tags that can be added for the subject at hand, please add them.
I use the SelectFields method on Loans to retrieve loan field data that is not in the reporting database in Encompass. It is very performant compared to opening loans up one by one but the results are returned as strings so it requires some parsing to get the values in their native types. Below is the example from the documentation for using this method.
using System;
using System.IO;
using EllieMae.Encompass.Client;
using EllieMae.Encompass.BusinessObjects;
using EllieMae.Encompass.Query;
using EllieMae.Encompass.Collections;
using EllieMae.Encompass.BusinessObjects.Loans;
class LoanReader
{
public static void Main()
{
// Open the session to the remote server
Session session = new Session();
session.Start("myserver", "mary", "maryspwd");
// Build the query criterion for all loans that were opened this year
DateFieldCriterion dateCri = new DateFieldCriterion();
dateCri.FieldName = "Loan.DateFileOpened";
dateCri.Value = DateTime.Now;
dateCri.Precision = DateFieldMatchPrecision.Year;
// Perform the query to get the IDs of the loans
LoanIdentityList ids = session.Loans.Query(dateCri);
// Create a list of the specific fields we want to print from each loan.
// In this case, we'll select the Loan Amount and Interest Rate.
StringList fieldIds = new StringList();
fieldIds.Add("2"); // Loan Amount
fieldIds.Add("3"); // Rate
// For each loan, select the desired fields
foreach (LoanIdentity id in ids)
{
// Select the field values for the current loan
StringList fieldValues = session.Loans.SelectFields(id.Guid, fieldIds);
// Print out the returned values
Console.WriteLine("Fields for loan " + id.ToString());
Console.WriteLine("Amount: " + fieldValues[0]);
Console.WriteLine("Rate: " + fieldValues[1]);
}
// End the session to gracefully disconnect from the server
session.End();
}
}
You will highly benefit from adding these fields to the reporting DB and using RDB query instead. Internally, Encompass has to open / parse files when you read fields without RDB, which is a slow process. Yet it just does a SELECT query on fields in RDB which is a very fast process. This tool will allow you quickly checking / finding which fields are in RDB so that you can create a plan for your query as well as a plan to update RDB: https://www.encompdev.com/Products/FieldExplorer
You query RDB via Session.Loans.QueryPipeline() very similarly to your use of Loan Query. Here's a good example of source code (in VB): https://www.encompdev.com/Products/AlertCounterFieldPlugin
Hi there I have a database of users who are able to receive emails for specific servers.
Table Ex:
UserName Server1 Server2 Server3
Jane.Doe True False True
Let me explain what exactly is happening, so I set a true or false to the server if they can receive emails or not for specific server.
Now a user submits a log and they pick a server they are reporting to. The Admins of that server will get an email.
However I don't want to hard code in (My boss got mad at me):
Example:
if (logSubmission.curServer == "Server1")
{
email = db.Users.Where(m => m.Server1 == true).Select(m => m.UserName).ToList();
}
So what I thought I could do is:
IEnumerable<SelectListItem> server = db.servers.Select(m => new SelectListItem
{
Value = m.server1,
Text = m.server1
});
string serverFixed = "";
foreach (var item in server)
{
if (item.Text == logSubmission.curServer)
{
serverFixed = item.Text;
serverFixed = serverFixed.Replace(" ", string.Empty);
}
}
I had a table called servers which holds all the servers.
So I call that and create a list of SelectListItem.
I take the Text of the server and match it with what the current server we are looking at.
Once That is done I had to remove spaces because some servers were like this: "Server 2"
And since my User Table Has it as "Sever2" I needed to remove spaces to match it.
Now I need to find a way to get that variable serverFixed into a similar look as:
email = db.Users.Where(m => m.Server1 == true).Select(m => m.UserName).ToList();
I feel like if I can change the .Where to somehow match the serverFixed I could do it.
Any ideas what I could do? Thanks!
Your Table is already hard coded. I'm not sure if that was a good decision to use several fields for servers instead of one foreign key field to a catalogue table with all servers. But maybe you had some reasons for it. I believe it's impossible to use some kind of variable for a field in LINQ. So I would propose to create an enum with all servers like
public enum ServerEnum {server1, server2, ...}
and than you can use it to identify your servers:
switch(logSubmission.curServer)
{
case (ServerEnum.Server.ToString()):
email = db.Users.Where(m => m.Server1 == true).Select(m => m.UserName).ToList();
break;
...
}
But anyway if you could change a structure of your tables that would be a better decision.
In this method, I am inserting a new item (Room) into the database. That process functions as expected.
But, in addition to that, each time I add a room, I want to add a piece of furniture as the initial piece. Each item of type Furniture has a "RoomID" to designate its location. Thus, Room contains a collection of Furniture. Below, I am the piece of "primary" furniture from the database, adding it to the room's furniture collection, and submitting changes. The room gets added to the database, but the Furniture.RoomID column remains as null.
public void AddResidentToUniverse(int residentID, int universeID)
{
Universe uni = _context.Universes.FirstOrDefault(u => u.UniverseID == universeID);
Resident res = _context.Residents.FirstOrDefault(r=>r.ResidentID == residentID);
if (uni != null && res!=null)
{
Room e = new Room();
Furniture primary = _context.Furnitures.FirstOrDefault(p => p.FurnitureID == new FurnitureController().GetPrimary(universeID).FurnitureID);
e.UniverseID = uni.UniverseID;
e.RoomName = res.RootName;
e.ResidentID = residentID;
e.Expired = null;
e.Furniture.Add(primary);
uni.Rooms.Add(e);
_context.SubmitChanges();
}
}
You need to add a line that tells your database what you want to insert. For example,
uni.Rooms.InsertOnSubmit(Room object);
uni.Furniture.InsertOnSubmit(furniture piece);
after this, you can write your
uni.SubmitChanges();
line.
I finally bit the bullet and erase my dbml, dropped and recreated the tables, and recreated my dbml. The Furniture.RoomID column updates correctly now. A totally unsatisfying, ham-handed and brute-force approach, I know.
I can add a contact to a address book but for some reason I can't remove it. The code I'm executing is as follows.
String abName = "Name ofthe targetted address book";
Outlook.Folder addressBook;
if (targetFolder.Folders.OfType<Outlook.Folder>().Any(element
=> element.Name == abName))
addressBook = targetFolder.Folders[abName] as Outlook.Folder;
else
addressBook = targetFolder.Folders.Add(
abName, Outlook.OlDefaultFolders.olFolderContacts) as Outlook.Folder;
addressBook.ShowAsOutlookAB = true;
for (int i = addressBook.Items.Count - 1; i >= 0; i--)
if (!stringList.Any(element
=> element == addressBook.Items.OfType<Outlook.ContactItem>()
.ToList()[i].Email1Address))
addressBook.Items.OfType<Outlook.ContactItem>().ToList().RemoveAt(i);
The fetching of the address book works and the matching for strings too. I get into the RemoveAt line for the exactly correct contacts. There's no error or other message when I execute the removal. Still, the contact list remain unaffected.
Why?
What can I do to actually remove the contacts?
I suspect that I may be working on a copy of the actual list containing the contacts. The problem is that if I don't create a List, I'm not sure how to alter the list of contacts.
So, the most helpful answer would shed some light on how to alter addressBook (or perhaps addressBook.Items) given certain condition. E.g., say that we'd like to remove all the contants the name of whom starts with the letter "Q".
At this moment I can only think of a super ugly work-around and it's so rectum-ugly that I don't even mention it here. Really ugly...
You ae not removing an Outlook contact. You are removing an OUtlook object from your own List object.
You need to call ContactItem.Delete.
As a side note, do not use multiple dot notation when working with COM objects, especially in a loop - you will receive a brand new COM object for each dot.
Here is a solution
private void ClearContact(Outlook.Application outlookApplication)
{
Outlook.MAPIFolder contactFolder = outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
int total = contactFolder.Items.Count;
while (total > 0)
{
// first index number is 1 not 0
var contact = (Outlook.ContactItem)contactFolder.Items[1];
contact.Delete();
total = contactFolder.Items.Count;
}
}
I use netoffice outlook api
http://netoffice.codeplex.com/wikipage?title=Outlook_Example05
And use while loop to delete all contact
I am writing a webpart for SharePoint 2010 that recuperates the latest page of a certain (custom) type, according to publishing date. It only takes into account pages tagged with a specified term. I would like it to be able to also do so with pages that are tagged with terms which are children of the selected terms.
If I have a term tree like so:
England
Kent
Dover
Canterbury
Surrey
Croydon
Crawley
then by selecting Kent, I want my webpart to show the latest page tagged with Kent, Dover, or Canterbury.
Is this possible in C# ?
Thanks for your time.
The function you are looking for is Term.GetTerms
You will need to get a TaxonomyValue from your field
Then you have to get the current TaxonomySession, then use the TaxonomySession to get the Term used in the field. From that term you can use the Parent field to get the parent Term.
Here is some rough code to show you the objects used.
TaxonomyFieldValue v = null; // Notsurehowtodothisbit();
TaxonomySession session = new TaxonomySession(site);
if (session.TermStores != null && session.TermStores.Count > 0)
{
TermStore termStore = session.TermStores[0];
Term t = termStore.GetTerm(v.TermGuid);
Term parentTerm = t.Parent;
TermCollection childTerms = t.GetTerms();
}
Once you have the tree, you may be able to use a caml query to generate a SPList.GetList query that brings back anything tagged that way.
I have not done an experiment in this regard...
But Bart-Jan Hoeijmakers has
private SPListItemCollection GetItemsByTerm(Term term, SPList list)
{
// init some vars SPListItemCollection items = null;
SPSite site = SPContext.Current.Site; // set up the TaxonomySession
TaxonomySession session = new TaxonomySession(site);
// get the default termstore TermStore termStore = session.TermStores[0];
// If no wssid is found, the term is not used yet in the sitecollection, so no items exist using the term
int[] wssIds = TaxonomyField.GetWssIdsOfTerm(SPContext.Current.Site, termStore.Id, term.TermSet.Id, term.Id, false, 1);
if (wssIds.Length > 0)
{
// a TaxonomyField is a lookupfield. Constructing the SPQuery
SPQuery query = new SPQuery();
query.Query = String.Format("<Where><Eq><FieldRef Name='MyTaxonomyField' LookupId='TRUE' /><Value Type='Lookup'>{0}</Value></Eq></Where>", wssIds[0]);
items = list.GetItems(query);
}
return items;
}
Nat's partial answer using the GetTerms method for the parent is great. The code for querying one list looks good too.
To get the id for the parent term, you can use TermStore.GetTerms against the title.
To search across all lists and libraries in the the site collection, you can use the Search API's FullTextSQLQuery method specifying the guids in the where clause with the owstaxIdMyTaxonomyField as the column.
There is a great example of getting id's by title and searching for a term store by id at Using taxonomy fields in SharePoint 2010: Part III