I have been able to programmatically add external (i.e. BDC) lookup fields to a list and I have also been able to add dependent external lookup fields to the same list. What I have not been able to figure out is how to programmatically add the dependent external lookup fields to the list's default view.
This article on MSDN gives an example of how to add a regular dependent lookup field to an SPView -but I have yet to find an example that shows how to programmatically add a dependent external lookup field to an SPView.
Below is the code in the FeatureActivated method of my EventReceiver that I am using to add the dependent external lookup fields to my SharePoint list and my attempt at adding the field to the list's default view.
var web = ((SPSite)properties.Feature.Parent).RootWeb;
var list = web.Lists.TryGetList("MyList");
var fldName = "EmployeeID";
var fld = list.Fields.CreateNewField("BusinessData", fldName) as SPBusinessDataField;
fld.SystemInstanceName = lobSystemInstanceName;
fld.EntityNamespace = entityNamespace;
fld.EntityName = entityName;
fld.BdcFieldName = entityFieldName;
//The dictionary object defined below contains key/value pairs that represent the
//field name as a string along with a boolean flag that specifies whether or not
//the secondary field should be added to the default view.
var secondaryFieldNames = new Dictionary<string, bool>()
{
{"FirstName", true},
{"LastName", true},
{"Title", false}
}
fld.SetSecondaryFieldsNames(secondaryFieldNames.Select(e => e.Key).ToArray());
var view = list.Views.DefaultView;
foreach (var secFld in secondaryFieldNames)
{
var viewFieldName = String.Format("{0}: {1}", fldName, secFld.Key);
if (!view.ViewFields.Exists(viewFieldName) && secFld.Value)
{
view.ViewFields.Add(viewFieldName);
view.Update();
}
}
As mentioned previously, the primary lookup field and all secondary lookup fields are successfully added to the list. The primary lookup field is successfully added to the list's default view. The seconday fields are not.
Storing the viewfield collection in a variable did the trick for me (don't really know why).
foreach (var secFld in secondaryFieldNames)
{
var viewFieldName = String.Format("{0}: {1}", fldName, secFld.Key);
var viewFields = view.ViewFields;
if (!viewFields.Exists(viewFieldName) && secFld.Value)
{
viewFields.Add(viewFieldName);
view.Update();
}
}
Related
When I try to create a new list item with the basic calendar/list fields everything works perfectly. However, when I try to do so with a "non-standard" field i.e. a field I added, I am getting a "field not recognized" error.
The field is clearly there! Is there some special way I need to populate these custom fields?
// get a specific list
ISiteListsCollectionPage list = await graphClient.Sites["root"].Lists.Request()
.Filter($"DisplayName eq 'Outlook Integration'").GetAsync();
// create a dictionary of [calendar] list properties
Dictionary<string, object> props = new Dictionary<string, object>();
// populate properties, all of these work just fine
props.Add("Title", evt.Subject);
props.Add("Location", evt.Location?.DisplayName);
props.Add("EventDate", utcStart.ToString("yyyy-MM-ddTHH:mm:ssK"));
props.Add("EndDate", utcEnd.ToString("yyyy-MM-ddTHH:mm:ssK"));
props.Add("Description", Regex.Replace(evt.Body.Content, "<.*?>", String.Empty)); // remove HTML content
// populate custom properties
props.Add("ResourceID", evt.Id); // throws error: Field 'ResourceID' is not recognized
// create list item with our properties dictionary
var newItem = new ListItem
{
Name = "My New Event",
Fields = new FieldValueSet()
{
AdditionalData = props
}
};
// call the service and get the result
var newListItem = await graphClient.Sites["root"].Lists[list[0].Id].Items.Request().AddAsync(newItem);
This is the complete list of fields on my list:
Here you can see the display name is "ResourceID" whereas the API name is "O365EventId." However, both result in the same error, "Field not recognized."
Note: ResourceID is one of the fields that I added. How can I set the value of this field via the Graph API?
Marc is right by saying in comment regarding column name, the provided screenshot displays Column.displayName which is
The user-facing name of the column.
but what actually FieldValueSet.AdditionalData expects as a key is Column.name which is:
The API-facing name of the column as it appears in the fields on a
listItem. For the user-facing name, see displayName.
In your case most likely displayName and name properties are different, you could verify it via following endpoint:
https://graph.microsoft.com/v1.0/sites/root/lists/Outlook Integration/columns
and that's the reason why this error occurs.
Via the Graph API client (C#), you can see a list of all columns for any given list like so:
// get specific list by name
ISiteListsCollectionPage list = await graphClient.Sites["root"].Lists.Request()
.Filter($"DisplayName eq 'YOUR_LIST_NAME_HERE'").GetAsync();
// get columns and output them to the log as a serialized object
var listColumns = await graphClient.Sites["root"].Lists[list[0].Id].Columns.Request().GetAsync();
logger.LogInformation($"List Columns Object: {JsonConvert.SerializeObject(listColumns).ToString()}");
I'm trying to retrieve only the attributes for a certain entity, that have changed metadata since the last metadataquery - for example: if a user changes the requirement on a certain field of a certain entity, and saves and publishes this change, I want a plugin that fires on message Publish & PublishAll to let me know what attribute and which metadata of that attribute has changed.
This is the code I have so far, based on this example on MSDN: https://msdn.microsoft.com/en-us/library/jj863605.aspx?f=255&MSPPError=-2147217396
I get the attributes for the three entities that are listed in includedEntities, so no problem there.
I get values for RequiredLevel and IsValidForAdvancedSearch, the two attribute properties listed in attributeProperties and the ones I want to watch while the rest returns null, so again, no problem here.
The attributeFilter also does what it's supposed to do: I only get datafields (attributes that do not describe another attribute), so once again: no problem.
The clientversionstamp I pass on is retrieved from a configurationparameter I created, which I update after every query. By watching it during debug, I know that it's the correct value - so I'm quite sure that's not the problem either.
So what is the problem? For each entity, I still have some (about half) of the attributes that are added to the collection of changed attributes in the response, although I didn't change anything.
If I do change something in metadata, that attribute does get added to the response collection as well, so my code does pick up the change. However, I still get a lot more data than I want - the goal is to only get that one attribute that has changed. What am I missing?
MetadataFilterExpression EntityFilter = new MetadataFilterExpression(LogicalOperator.And);
EntityFilter.Conditions.Add(new MetadataConditionExpression("LogicalName", MetadataConditionOperator.In, includedEntities));
MetadataPropertiesExpression EntityProperties = new MetadataPropertiesExpression()
{
AllProperties = false
};
EntityProperties.PropertyNames.AddRange(new string[] { "Attributes" });
MetadataConditionExpression optionsetAttributeName = new MetadataConditionExpression("AttributeOf", MetadataConditionOperator.Equals, null);
MetadataFilterExpression AttributeFilter = new MetadataFilterExpression(LogicalOperator.And);
AttributeFilter.Conditions.Add(optionsetAttributeName);
MetadataPropertiesExpression AttributeProperties = new MetadataPropertiesExpression() { AllProperties = false };
foreach (string attrProperty in attributeProperties)
{
AttributeProperties.PropertyNames.Add(attrProperty);
}
EntityQueryExpression entityQueryExpression = new EntityQueryExpression()
{
Criteria = EntityFilter,
Properties = EntityProperties,
AttributeQuery = new AttributeQueryExpression()
{
Properties = AttributeProperties,
Criteria = AttributeFilter
}
};
RetrieveMetadataChangesRequest req = new RetrieveMetadataChangesRequest()
{
Query = entityQueryExpression,
ClientVersionStamp = clientVersionStamp
};
return (RetrieveMetadataChangesResponse)service.Execute(req);
I need to programmatically retrieve the columns in a Sharepoint document library, in order to set file properties externally to Sharepoint.
I've found that setting the metadata property is not hard as long as you already know the name of the column, which I cannot expect users to input themselves.
As it does not seem possible to do this through the Sharepoint Web Services I have created my own custom web service so I have access to the Client Object Model.
Using this code I am able to retrieve the custom columns I have created, however I am not able to distinguish between the ones editable in the item properties section (picture above) and those which aren't.
SPList list = web.Lists[specificList];
foreach (SPField field in list.Fields)
{
if (!field.Hidden)
{
var title = field.Title;
var description = field.Description;
var parentList = field.ParentList;
var references = field.FieldReferences; // contains names of fields referenced in computed fields
if (references != null)
{
foreach (string reference in references)
{
var test = parentList.Fields.GetField(reference);
}
}
}
}
I get extra properties such as:
Copy Source
Content Type
Checked Out To
Checked In Comment
Type
File
Size
Edit
Version
Source Version
Source Name
I have also tried retrieving the column fields from the SPFolder item, but again this returns many extra properties and is even less filterable.
foreach (SPListItem folderItem in list.Folders)
{
SPFolder folder = folderItem.Folder;
System.Collections.Hashtable oHashtable = folder.Properties;
System.Collections.ICollection collKeys = oHashtable.Keys;
foreach (var key in collKeys)
{
string keyName = key.ToString();
}
}
Is there a standard way to retrieve the column fields I need? Or will I have to manually exclude the defaults ones such as "Checked out to"?
First you have to know which form you are viewing. Is it the EditForm or NewForm?
You can filter the columns visible on a specific form by getting the fields of the ContentType and then check if they are getting displayed on the NewForm (or whatever form):
SPList list = web.Lists[specificList];
var contentType = list.ContentTypes[0]; // Select first contenttype. Change this if you need a different contentType
foreach (SPField field in contentType.Fields)
{
if (!field.Hidden
&& (field.ShowInEditForm == null
|| !field.ShowInEditForm.Value)) // Replace ShowInEditForm with the form you need
{
var title = field.Title;
var description = field.Description;
var parentList = field.ParentList;
var references = field.FieldReferences; // contains names of fields referenced in computed fields
if (references != null)
{
foreach (string reference in references)
{
var test = parentList.Fields.GetField(reference);
}
}
}
}
I think the best way to go is to get the fields from the content type and not the list itself. That way you'll get only the fields visible in the form.
var list = web.Lists[specificList];
var contentType = list.ContentTypes["Document"];
foreach (SPField field in contentType.Fields)
{
if(!field.Reorderable || contentType.FieldLinks[field.Id].Hidden)
{
continue;
}
//Process fields
}
You may ask "Why Reordable=false?". Well, generally custom fields do not set this property so it is a nice way to filter them.
Also I didn't invent this code. This code is taken from code behind class of SharePoint standard content type fields reorder page (using reflection).
Hi I have two Lists in sharepoint 2007.
I have a lookup column in on list which looks the other field.
I want to use the sharepoint object model to add an item to the second list.
How to i set the lookup field value. (The value is already in the other list).?
SPListItem Employee = web.Lists["Employee"].Items.Add();
Employee["Name"] = account.Name;
Employee["Department"] = <lookup value must come here>
Employee.Update();
Lookup fields will contain a combination of the row's id and the value of the column to display, separated by :#, in your case that could be 1:#HumanResources or 12:#Engineering.
So to reference a lookup simply setting the id won't be enough, instead the above mentioned string needs to be set. Luckily SharePoint provides the class SPFieldLookupValue that does exactly this:
var department = web.Lists["Department"].GetItemById(1);
var employee = web.Lists["Employee"].Items.Add();
employee["Name"] = account.Name;
employee["Department"] = new SPFieldLookupValue(department.ID, department.Title);
employee.Update();
I've been having a problem for some time, and I've exhausted all means of figuring this out for myself.
I have 2 lists in a MS Sharepoint 2010 environment that are holding personal physician data for a medical group...nothing special just mainly text fields and a few lookup choice fields.
I am trying to write a program that will migrate the data over from List A to List B. I am using LINQ to Sharepoint to accomplish this. Everything compiles just fine, but when it runs and hits the SubmitChanges() method, I get a runtime error that states:
"All new entities within an object graph must be added/attached before changes are submitted."
this issue must be outside of my realm of C# knowledge because I simply cannot find the solution for it. The problem is DEFINITELY stemming from the fact that some of the columns are of type "Lookup", because when I create a new "Physician" entity in my LINQ query, if I comment out the fields that deal with the lookup columns, everything runs perfectly.
With the lookup columns included, if I debug and hit breakpoints before the SubmitChanges() method, I can look at the new "Physician" entities created from the old list and the fields, including data from the lookup columns, looks good, the data is in there the way I want it to be, it just flakes out whenever it tries to actually update the new list with the new entities.
I have tried several methods of working around this error, all to no avail. In particular, I have tried created a brand new EntityList list and calling the Attach() method after each new "Physician" Entity is created, but to no avail, it just sends me around in a bunch of circles, chasing other errors such as "ID cannot be null", "Cannot insert entities that have been deleted" etc.,
I am no farther now than when I first got this error and any help that anyone can offer would certainly be appreciated.
Here is my code:
using (ProviderDataContext ctx = new ProviderDataContext("http://dev"))
{
SPSite sitecollection = new SPSite("http://dev");
SPWeb web = sitecollection.OpenWeb();
SPList theOldList = web.Lists.TryGetList("OldList_Physicians");
//Create new Physician entities.
foreach(SPListItem l in theOldList.Items)
{
PhysiciansItem p = new PhysiciansItem()
{
FirstName = (String)l["First Name"],
Title = (String)l["Last Name"],
MiddleInitial = (String)l["Middle Init"],
ProviderNumber = Convert.ToInt32(l["Provider No"]),
Gender = ConvertGender(l),
UndergraduateSchool =(String)l["UG_School"],
MedicalSchool = (String)l["Med_School"],
Residency = (String)l["Residency"],
Fellowship = (String)l["Fellowship"],
Internship = (String)l["Internship"],
PhysicianType = ConvertToPhysiciantype(l),
Specialty = ConvertSpecialties(l),
InsurancesAccepted = ConvertInsurance(l),
};
ctx.Physicians.InsertOnSubmit(p);
}
ctx.SubmitChanges(); //this is where it flakes out
}
}
//Theses are conversion functions that I wrote to convert the data from the old list to the new lookup columns.
private Gender ConvertGender(SPListItem l)
{
Gender g = new Gender();
if ((String)l["Sex"] == "M")
{
g = Gender.M;
}
else g = Gender.F;
return g;
}
//Process and convert the 'Physician Type', namely the distinction between MD (Medical Doctor) and
//DO (Doctor of Osteopathic Medicine). State Regualtions require this information to be attached
//to a physician's profile.
private ProviderTypesItem ConvertToPhysiciantype(SPListItem l)
{
ProviderTypesItem p = new ProviderTypesItem();
p.Title = (String)l["Provider_Title:Title"];
p.Intials = (String)l["Provider_Title"];
return p;
}
//Process and convert current Specialty and SubSpecialty data into the single multi-choice lookup column
private EntitySet<Item> ConvertSpecialties(SPListItem l)
{
EntitySet<Item> theEntityList = new EntitySet<Item>();
Item i = new Item();
i.Title = (String)l["Provider Specialty"];
theEntityList.Add(i);
if ((String)l["Provider SubSpecialty"] != null)
{
Item theSubSpecialty = new Item();
theSubSpecialty.Title = (String)l["Provider SubSpecialty"];
theEntityList.Add(theSubSpecialty);
}
return theEntityList;
}
//Process and add insurance accepted.
//Note this is a conversion from 3 boolean columns in the SP Environment to a multi-select enabled checkbox
//list.
private EntitySet<Item> ConvertInsurance(SPListItem l)
{
EntitySet<Item> theEntityList = new EntitySet<Item>();
if ((bool)l["TennCare"] == true)
{
Item TenncareItem = new Item();
TenncareItem.Title = "TennCare";
theEntityList.Add(TenncareItem);
}
if ((bool)l["Medicare"] == true)
{
Item MedicareItem = new Item();
MedicareItem.Title = "Medicare";
theEntityList.Add(MedicareItem);
}
if ((bool)l["Commercial"] == true)
{
Item CommercialItem = new Item();
CommercialItem.Title = "Commercial";
theEntityList.Add(CommercialItem);
}
return theEntityList;
}
}
So this may not be the answer you're looking for, but it's what's worked for me in the past. I've found that updating lookup fields using Linq to Sharepoint to be quite frustrating. It frequently doesn't work, or doesn't work efficiently (forcing me to query an item by ID just to set the lookup value).
You can set up the entity so that it has an int property for the lookup id (for each lookup field) and a string property for the lookup value. If, when you generate the entities using SPMetal, you don't generate the list that is being looked up then it will do this on it's own. What I like to do is (using your entity as an example)
Generate the entity for just that one list (Physicians) in some temporary folder
Pull out the properties for lookup id & value (there will also be private backing fields that need to come along for the ride too) for each of the lookups (or the ones that I'm interested in)
Create a partial class file for Physicians in my actual project file, so that regenerating the entire SPMetal file normally (without restricting to just that list) doesn't overwrite changes
Paste the lookup id & value properties in this partial Physicians class.
Now you will have 3 properties for each lookup field. For example, for PhysicianType there will be:
PhysicianType, which is the one that is currently there. This is great when querying data, as you can perform joins and such very easily.
PhysicianTypeId which can be occasionally useful for queries if you only need ID as it makes it a bit simpler, but mostly I use it whenever setting the value. To set a lookup field you only need to set the ID. This is easy, and has a good track record of actually working (correctly) in my experiences.
PhysicianTypeValue which could be useful when performing queries if you just need the lookup value, as a string (meaning it will be the raw value, rather than something which is already parsed if it's a multivalued field, or a user field, etc. Sometimes I'd rather parse it myself, or maybe just see what the underlying value is when doing development. Even if you don't use it and use the first property, I often bring it along for the ride since I'm already doing most of the work to bring the PhysicianTypeId field over.
It seems a bit hacky, and contrary to the general design of linq-to-SharePoint. I agree, but it also has the advantage of actually working, and not actually being all that hard (once you get the rhythm of it down and learn what exactly needs to be copied over to move the properties from one file to another).