How to create/update attributes for a document using c#?

I wanted to create documents and set their attributes in ProjectWise from a C# application. I could create documents but I don't know how to set the attributes. I'm trying to use aaApi_SetLinkDataColumnValue and aaApi_CreateLinkData but it does not work.

Parents
  • Hi Dan,

    Thanks for your reply. To use CreateEnvAttr(), I need to define  AAEALINKAGE structure in c# and I cannot get this part to work. I was wondering if you can help me with this.

    Thanks again

  • This function is difficult to do in C#.  The LPAAEALINKAGE is a pointer to a structure which contains a pointer to a structure.  You are passing a managed nested structure to an un-managed function.   Each structure must be tagged with [StructLayout(LayoutKind.Sequential)], each variable must be the correct size and you have to allocate the instance of the structures with Marshal.AllocHGlobal to prep the memory to be sent to an unmanaged function.  I have only used this function from C++, not C#.  Don't forget to release the allocated memory.

  • What version of ProjectWise are you using?  Best to include the version numbers for ProjectWise Explorer, ProjectWise SDK and for your ProjectWise Integration Server.

    Also, what are you using to compile and link your code?  You should use Visual Studio 10 for V8i development and Visual Studio 15 for CONNECT editions, but you can make most versions of Visual Studio "work" as long as you take care of any dependencies based on those development/deployments assumptions.  And what OS are you running ProjectWise Explorer and Visual Studio upon?

    I'll have to take a closer look at your code to see if I can reproduce your error, but I would rather do it with the same versions of ProjectWise and Visual Studio that you are using.

  • I am using ProjectWise Explore V8i (Version 08.11.11.590), Visual Studio 2017 and ProjectWise SDK 8.11.11 SS4(RU)on Windows 10. I haven't had any problem reading data (Projects/Documents/Attributes) so far.

    Parvaneh

  • Windows 10 isn't a supported OS for the version of ProjectWise that you are using, nor is Visual Studio 2017, so you really should reconsider your development environment if you intend to use your customization for a production datasource. 

    That said, I'll try to look at your code in a CONNECT edition environment and if I get a chance, I'll try it in a V8i environment using supported tools and OS.

    In the meantime, if you can, you might try your code on a supported OS with VS 2010 and see if the behavior changes.

  • Parvaneh,

    OK, I didn't notice that your code snippet was in C#, sorry about that! However, I did notice that you set the value of pv_PWDocumentID to 2, but later you used the value of PWDocumentID when you selected your links, but you didn't specify a value for that variable.

    I didn't actually try your code with C# as I don't have what you defined via PInvoke, etc.

    I think that you might be confusing column number with a column's value, but again, I didn't try your code.

    I took the sample code that I provided earlier (with some changes to the comments) with VS 2015 C++, ProjectWise CONNECT Edition Integration Server, Explorer and SDK v10.00.03.49, running on a Windows Server 2012 R2 and it works without error.

    Here's my C++ code (using MFC):

    Again, please try this with C++ if you can (and using supported OS and development tools), and then try to make it work with C#. The problem you are having could very well be how you marshal, etc. those API calls, enums, etc.

    extern "C" int WINAPI DocCmd_testcode
    (
    	unsigned int count,
    	long*  pProjects,
    	long*  pDocuments
    )
    {
    	AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
    	// for each document selected
    	for (LONG i = 0; i < (LONG)count; i++)
    	{
    		// We need some information about the environment.
    		LONG	lEnvId = 0L;
    		LONG	lTableId = 0L;
    		LONG	lAttrColId = 0L;    // the column id that holds the attribute id's value
    
    		if (!aaApi_GetEnvTableInfoByProject(*(pProjects + i), &lEnvId, &lTableId, &lAttrColId))
    		{
    			aaApi_SetLastError(AAERR_USERERROR_FIRST + 11,
    				L"Could not get environment info.",
    				L"Problem with selecting environment info using project id.");
    			aaApi_ShowLastError();
    			return AAERR_USERERROR_FIRST + 11;
    		}
    
    		/*
    		We need to determine the column ids that we will need for our values. Since
    		the documents passed to use could be from different folders with different
    		environments, we will look these up for each document to keep things simple.
    		*/
    		LONG	lo_string10ColId = 0L;
    		LONG	lo_string40ColId = 0L;
    
    		LONG lColCount = aaApi_SelectColumnsByTable(lTableId);
    		if (lColCount == 0)
    		{
    			aaApi_SetLastError(AAERR_USERERROR_FIRST + 12,
    				L"Could not get column info.",
    				L"Table appears to have no columns!");
    			aaApi_ShowLastError();
    			return AAERR_USERERROR_FIRST + 12;
    		}
    		else if (lColCount == -1)
    		{
    			aaApi_SetLastError(AAERR_USERERROR_FIRST + 13,
    				L"Could not get column info.",
    				L"Problem with selecting column info using table id.");
    			aaApi_ShowLastError();
    			return AAERR_USERERROR_FIRST + 13;
    		}
    
    		// sentinel value of zero...
    		lo_string10ColId = 0L;
    		lo_string40ColId = 0L;
    		for (LONG j = 0; j < lColCount; j++)
    		{
    			if (_tcsicmp(_T("o_string10"), aaApi_GetColumnStringProperty(COLUMN_PROP_NAME, j)) == 0)
    			{
    				lo_string10ColId = aaApi_GetColumnNumericProperty(COLUMN_PROP_COLUMN_ID, j);
    			}
    			else if (_tcsicmp(_T("o_string40"), aaApi_GetColumnStringProperty(COLUMN_PROP_NAME, j)) == 0)
    			{
    				lo_string40ColId = aaApi_GetColumnNumericProperty(COLUMN_PROP_COLUMN_ID, j);
    			}
    		}
    
    		/*
    		If any of our column ids are zero, the environment didn't contain it, so
    		report an error and return.
    		*/
    		if ((lo_string10ColId == 0) || (lo_string40ColId == 0))
    		{
    			aaApi_SetLastError(AAERR_USERERROR_FIRST + 14,
    				L"Could not determine column ids.",
    				L"Environment table is missing one or more target columns.");
    			aaApi_ShowLastError();
    			return AAERR_USERERROR_FIRST + 14;
    		}
    
    		/*
    		Now we have the information we need to update any existing rows for the
    		current document, so we will need to create a loop to process all the
    		existing rows.
    		*/
    		LONG lLinkRows = aaApi_SelectLinks(*(pProjects + i), *(pDocuments + i));
    		if (-1 == lLinkRows)
    		{
    			aaApi_SetLastError(AAERR_USERERROR_FIRST + 15,
    				L"Could not select document sheets.",
    				L"In the real world, we would also want to know the PW error here.");
    			aaApi_ShowLastError();
    			return AAERR_USERERROR_FIRST + 15;
    		}
    		else if (0 == lLinkRows)
    		{
    			// no rows to update - skip this document...
    			continue;
    		}
    
    		/*
    		Process each existing row
    		*/
    		for (LONG k = 0; k < lLinkRows; k++)
    		{
    			// For each existing row, we first need to initialize the "Update" buffer.
    			aaApi_FreeLinkDataUpdateDesc();
    
    			/*
    			Populate the update buffer with the data that we want to write.  You
    			must always change the data into a string and account for the correct format
    			if using dates.  For this exercise we are only updating string data.
    			*/
    
    			// attribute column named o_string10
    			if (!aaApi_UpdateLinkDataColumnValue(lTableId, lo_string10ColId, _T("Updated!")))
    			{
    				aaApi_SetLastError(AAERR_USERERROR_FIRST + 16,
    					L"Could not populate update buffer for o_string10.",
    					L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError();
    				return AAERR_USERERROR_FIRST + 16;
    			}
    
    			// initialize the string buffer that you will pass to the API
    			WCHAR   szDSName[256];	// make sure that the buffer is large enough for your data (of course)
    			memset(szDSName, '\0', sizeof(szDSName) / sizeof(WCHAR));
    			aaApi_GetActiveDatasourceName(szDSName, sizeof(szDSName) / sizeof(WCHAR));
    
    			// attribute column named o_string40
    			if (!aaApi_UpdateLinkDataColumnValue(lTableId, lo_string40ColId, szDSName))
    			{
    				aaApi_SetLastError(AAERR_USERERROR_FIRST + 17,
    					L"Could not populate update buffer for o_string40.",
    					L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError();
    				return AAERR_USERERROR_FIRST + 17;
    			}
    
    			// update the existing row using the value for the a_attrno column for this row
    			// this will use the update buffer that we just populated
    			LPCWSTR lpcwstrColumnValue = aaApi_GetLinkStringProperty(LINK_PROP_COLUMN_VALUE, k);
    			if (!aaApi_UpdateLinkData(lTableId, lAttrColId, lpcwstrColumnValue))
    			{
    				aaApi_SetLastError(AAERR_USERERROR_FIRST + 18,
    					L"Could not update attribute row.",
    					L"In the real world, we would also want to know the PW error here.");
    				aaApi_ShowLastError();
    				return AAERR_USERERROR_FIRST + 18;
    			}
    		} // for each sheet
    	} // for each document
    
    	return 0;  // all documents were successfully processed!
    }
    

  • Does that column ID exist in that table. Are you sure you have the right Table ID?  I found some old C++ code that we still use in production. This is the order of things done there:

    aaApi_SelectEnvByProjectId (lProjectId)

    lEnvId = aaApi_GetEnvId (0)

    lTableId = aaApi_GetEnvNumericProperty (ENV_PROP_TABLEID, 0)

    lNumLinks = aaApi_SelectLinks(lProjectId, lDocumentId)

    if (lNumLinks > 0L)

        // Get the attribute id from the link table

         lAttrNo = _ttol (aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, 0))  //Converts String to Long which is Int in C#

          // Get the attribute table column id from the link table
         lColNo = aaApi_GetLinkNumericProperty (LINK_PROP_COLUMNID, 0)

          aaApi_SelectLinkData (lTableId, lColNo, aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, 0), &lNumCols))

         LOOP from i=0 to lNumCols, increment of 1

            IF aaApi_GetLinkDataColumnStringProperty (LINKDATA_PROP_COLUMN_NAME, i) = "sheet_num" Then

                  aaApi_FreeLinkDataUpdateDesc();

                   lColumnId = aaApi_GetLinkDataColumnNumericProperty(LINKDATA_PROP_COLUMNID, i)

                   aaApi_UpdateLinkDataColumnValue(lTableId, lColumnId, _T("B"))

                   aaApi_UpdateEnvAttr(lTableId, lAttrNo);

                  return()

             End IF

           END LOOP

Reply
  • Does that column ID exist in that table. Are you sure you have the right Table ID?  I found some old C++ code that we still use in production. This is the order of things done there:

    aaApi_SelectEnvByProjectId (lProjectId)

    lEnvId = aaApi_GetEnvId (0)

    lTableId = aaApi_GetEnvNumericProperty (ENV_PROP_TABLEID, 0)

    lNumLinks = aaApi_SelectLinks(lProjectId, lDocumentId)

    if (lNumLinks > 0L)

        // Get the attribute id from the link table

         lAttrNo = _ttol (aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, 0))  //Converts String to Long which is Int in C#

          // Get the attribute table column id from the link table
         lColNo = aaApi_GetLinkNumericProperty (LINK_PROP_COLUMNID, 0)

          aaApi_SelectLinkData (lTableId, lColNo, aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, 0), &lNumCols))

         LOOP from i=0 to lNumCols, increment of 1

            IF aaApi_GetLinkDataColumnStringProperty (LINKDATA_PROP_COLUMN_NAME, i) = "sheet_num" Then

                  aaApi_FreeLinkDataUpdateDesc();

                   lColumnId = aaApi_GetLinkDataColumnNumericProperty(LINKDATA_PROP_COLUMNID, i)

                   aaApi_UpdateLinkDataColumnValue(lTableId, lColumnId, _T("B"))

                   aaApi_UpdateEnvAttr(lTableId, lAttrNo);

                  return()

             End IF

           END LOOP

Children
No Data