Calculate a new content type field - c#

This is my first post ever here after many years of finding solutions to my issues.
I'm rather new to SharePoint, though not a complete beginner.
Here is what I want to achieve:
I want to create a list, which will be filled by users
This list will contain an Identifier text field, we'll call it strId
This list must contain a field, that's a value retrieved from an oracle database using strId as a parameter
I already did some BDC Models but this seems to be different, as the list elements do not come from a database, just one column does.
I thought about creating a content Type with 2 site columns in it, one with the strId and the other would do the calculation, but I can't seem to be able to do it.
Can anyone help on this matter ?

I would suggest the following:
create external content list : (eg: T_Investigator)
create new custom list (eg: ExternalDisplay)
add your strid as a single line of text
add the external content list as a lookup (eg: external_id), and add the field you wish to see as well (eg: firstname, lastname)
create a sharepoint solution with an eventreceiver with onAdding/onUpdating for the ExternalDisplay list. On adding / updating, you can overwrite the 'external_id' field with the new value in your strid
code:
using Microsoft.SharePoint;
namespace SOEventReceiver.ExternalTest
{
public class ExternalTest : SPItemEventReceiver
{
public override void ItemAdding(SPItemEventProperties properties)
{
string strid = (properties.AfterProperties["strid"] ?? string.Empty).ToString();
if (!string.IsNullOrWhiteSpace(strid))
{
properties.AfterProperties["external_id"] = strid + ";#" + strid;
}
base.ItemAdding(properties);
}
public override void ItemUpdating(SPItemEventProperties properties)
{
string strid = (properties.AfterProperties["strid"] ?? string.Empty).ToString();
if (!string.Equals(strid, properties.ListItem["strid"] ?? string.Empty))
{
properties.AfterProperties["external_id"] = strid + ";#" + strid;
}
base.ItemUpdating(properties);
}
}
}
with elements.xml pointing to the url of your list
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Receivers ListUrl="Lists/ExternalDisplay">
<Receiver>
<Name>ExternalTestItemAdding</Name>
<Type>ItemAdding</Type>
<Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
<Class>SOEventReceiver.ExternalTest.ExternalTest</Class>
<SequenceNumber>10000</SequenceNumber>
</Receiver>
<Receiver>
<Name>ExternalTestItemUpdating</Name>
<Type>ItemUpdating</Type>
<Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
<Class>SOEventReceiver.ExternalTest.ExternalTest</Class>
<SequenceNumber>10000</SequenceNumber>
</Receiver>
</Receivers>
</Elements>
Sharepoint automatically makes the changes since the Lookupfield value has changed, and the external columns are displayed.
The advantage here would be that sharepoint does the main magic, it also doesn't matter which kind of lookup list is behind this (external/sharepoint).
A disadvantage is that the external_id field could also be changed, which wouldn't update the strid field, and you would get inconsistent data. This you could however, also block inside the updating statement

Related

Retrieve metadata column fields

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).

Display class object in GridView

I have such a situaton, that I am reading txt file making some operation on the lines and at the end I want to display everything in gridview. I have 3 separated columns. In first and second one I am displaying normal string values. But in middle one I have object returned by one class and I would like to display it normally in my gridview. How can I achieve it? I have something like this so far.
while ((line = sr.ReadLine()) != null)
{
string[] lines = line.Split(",".ToCharArray());
object returnValue;
MyColumns object = new MyColumns();
object.Time = line[0];
object.System_Description = line[1];
object.User_Description = line[2];
///earlier in my code I have object of class called method
returnValue = method.MyMethod(mc.System_Description);
Class main = new Class();
main.Data1= object.Time;
main.ProblemData= returnValue;
main.Data2= object.User_Description;
list3.Add(main);
}
this.dataGridView3.DataSource = list3;
I have problem with showing ProblemData. Now in this column gridview shows me "project_name.Class_Name" (name of the class that this value was retured by)
EDIT:
Ok, I also have to mention that this class, from which returnValue gets values has 5 properties, let's say Categry, Name, Second_Name, Status and Value. This returnValue holds all this 5 properties with their current values.
EDIT2: Maybe someone knows how to display all this fields in one column? How can I join them only for displaying purpose? When I make normal List and insert this returnValue, it creates these 5 columns and insert values inside. Maybe it will make it easier to understand.
Please see my first comment on your question.
You have to use a nested GridView inside your second column which will bind to the returnValue. This is because GridView cannot automatically cascade your object datasource. The inner binding needs to be done in the RowDataBound event of your main GridView. For this to work, you will have to re-organise / re-factor your code.
Alternatively, you can concatenate the properties of the returnValue if their string representations can work for your scenario.
Edit:
The OP is asking about WinForms DataGridView (not ASP.Net):
The WinForms DataGridView does not support nesting out-of-the-box. However, there are some templating workarounds which are complicated. You are looking for a simple solution. I found one which can serve your immediate needs.
Hook into the CellFormatting event.
if (e.value is YOUR_OBJECT_TYPE) {
e.Value = (e.Value as YOUR_OBJECT_TYPE).YOUR_PROPERTY_NAME;
}
For details please refer to this: Binding to Nested Properties
Alternate option:
The alternate option of concatenating the properties of the returnValue as string, will also work.
main.ProblemData = "Cat: " + returnValue.Category + ", Name: " + returnValue.Name;
you should have defined your class variables like a propertiesbecause you are using them in databinding. like this..
public String Data1 {get;set;}
also make your list a ObservableCollection as it will notify the view whenever you change something in your list..
Two options
Override ToString() method in your ProblemData type
public class ProblemData
{
//whatever...
public override string ToString()
{
return string.format("{0}", this.SomeObject); //set proper display
}
}
public class YourClass()
{
//...
public ProblemData ProblemData{ get; set;}
}
Or you can set grid column formatter if object type can be formatted using string.Format
dataGridView3.Columns["ProblemData"].DefaultCellStyle.Format = "N";
//display string.Format({0:N}

LINQ to SharePoint 2010 getting error "All new entities within an object graph must be added/attached before changes are submitted."

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).

Add hyperlink to automatically generated XML in Silverlight (C#)

Recently, my question here was answered. Now that I've got my XML all parsed and looking pretty, I've got another question about my application I've been banging my head against a wall over the past few day(s).
The XML is used to automatically add Artist names to a listbox. What I want to do is provide links to Amazon searches from these artists. In the following function, the XML is parsed and the artist name is then added to the list. I need to somehow put a hyperlink on this artist name. Does anybody know how this would be possible?
EDIT: I am missing the connection between steps 2 and 3 in the answer that has been provided. Also, I do not understand how number 3 works at all. I must admit I'm a neophyte at Silverlight programming. From my understanding, you do the binding in the XAML page. How can this be done for listbox items that have not even been created yet?
Additionally, I realized something that the Amazon URLs use + signs where spaces are in artist names. I edited the code to reflect that. Please understand that having the hyperlink as text under each artist name is not what I'm going after. ;)
public void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null || e.Error.Message.IndexOf("NotFound") == -1)
{
ArtistsList.Items.Clear();
uname.Text = "Try Another One!";
XDocument doc = XDocument.Parse(e.Result);
var topArtists = from results in doc.Descendants("artist")
select results.Element("name").Value.ToString();
foreach (string artist in topArtists)
{
ArtistsList.Items.Add(artist);
string amazonPlus = artist.Replace(" ", "+");
string amazonURL = "http://www.amazon.ca/s/ref=nb_ss_gw?url=search-alias%3Daps&field-keywords=" + amazonPlus + "&x=0&y=0";
ArtistsList.Items.Add(amazonURL);
}
}
}
EDIT 2 Is there anybody who can clarify the answer provided?
1) Create an Artist object with a Name and Amazon Url Property
2) When you parse the XML, create a collection of items using LINQ.
var topArtists = from result in doc.Descendants("artists")
select new Artist
{
Name = result.Element("name").Value,
Amazon = new Uri(string.format("http://amazon.com/artist={0}", result.Element("name").Value), UriKind.Absolute),
};
ArtistList.ItemsSource = topArtists;
3) I would then use a data template to bind the Name to a TextBlock
Text or HyperlinkButton Content and the Amazon property to the
HyperlinkButton.NavigateUrl.

How to setup a property from 1 linq2sql obj that is populated by a relationship to another obj

I'm using Linq2SQL and I'm pretty new to it.
I've got a User table and a UserData table, the idea being that properties for the User object can be added / removed by adding or removing rows in the UserData table. I did not come up with this particular design but I am more or less stuck with it (as long as I can come up with a solution)
alt text http://www.86th.org/linq2sqlproblem.jpg
I'd like to populate/bind "FirstName" on the User object by something along the lines of setting the value to:
UserData.Value WHERE UserData.ItemID == User.UserID AND KeyName = 'FirstName'
Similarly, LastName would be:
UserData.Value WHERE UserData.ItemID == User.UserID AND KeyName = 'LastName'
Description of the UserData Table:
UserData.ItemID is the FK (User.UserID)
UserData.KeyName is specifying the name of the property
UserData.Value is the actual value.
How would I setup my User object to handle this so I could do the normal CRUD functionality on this object and have the changes carry through to both tables?
Is this even possible?
Is this bad form?
Personally I feel its bad form but I suppose everyone has there way of doing things. Why can't you assign userdata in the users table? I think I might not be understanding the design idea here.
Quick Note
I renamed UserData to ExtendedProperty and this caused the relationship from User to ExtendedProperty to be called ExtendedProperties.
Summary of changes
Created a getter/setter for both FirstName and LastName in the partial User class
Grabbed the correct ExtendedProperty element out of the ExtendedProperties collection and either returned or updated the Value property of it.
Refactored into a reusable format as shown below
partial class User
{
public string FirstName
{
get { return (string)this.getExtendedProperty("FirstName").Value; }
set { this.getExtendedProperty("FirstName").Value = value; }
}
public string LastName
{
get { return (string)this.getExtendedProperty("LastName").Value; }
set { this.getExtendedProperty("LastName").Value = value; }
}
// Grab a related property out of the collection, any changes to it will be reflected in the database after a submit
private ExtendedProperty getExtendedProperty(string KeyName)
{
// grab the properties that fit the criterea
var properties = (from prop in this.ExtendedProperties where prop.KeyName == KeyName select prop);
// return value
ExtendedProperty property = properties.SingleOrDefault();
// if this is a new user then there arent going to be any properties that match
if (property == null)
{
// Define a new item to add to the collection
property = new ExtendedProperty()
{
ItemID = this.UserID,
KeyName = KeyName,
Value = String.Empty
};
// Add the item we're about to return to the collection
this.ExtendedProperties.Add(property);
}
// either way we have a valid property to return at this point
return property;
}
}
I just hope this isn't bloated / grossly inefficient.
Edit
In getExtendedProperty, it would error when setting the FirstName or LastName of a newly created User because it would not have any corresponding ExtendedProperty elements in the ExtendedProperties collection as shown below.
User expected = new User();
expected.UserID = Guid.NewGuid();
expected.UserName = "LJ";
expected.FirstName = "Leeroy"; // It would error here
expected.LastName = "Jenkins";
Because of this I added a check to ensure that new items get added to the ExtendedProperties collection if they are requested and not currently in there.
I also removed setExtendedProperty since I felt it wasn't necessary and was just a method around a 1 liner anyway.
I would really appreciate any feedback before I accept this answer, I'll let it sit for a few days.

Categories