Entity Framework - IQueryable to DataTable - c#-4.0

I'm trying to convert an IQueryable object to a DataTable. Here's an example of a query that I would like to convert to a DataTable:
var query = DbContext.SomeObjectSet.Select(x => new { PropertyName1 = x.ColumnName1, PropertyName2 = x.ColumnName2 });
Please note the anonymous object that is created in the Select method and the names of the properties:
new { PropertyName1 = x.ColumnName1, PropertyName2 = x.ColumnName2 }
After Googling this issue, I came across the following code that converts an IQueryable object to a DataTable:
public static DataTable EntityToDatatable(this IQueryable result)
{
ObjectQuery query = (result as ObjectQuery);
ObjectContext context = query.Context;
EntityConnection entityCon = (context.Connection as EntityConnection);
using (SqlConnection sqlCon = new SqlConnection(entityCon.StoreConnection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(query.ToTraceString(), sqlCon))
{
foreach (var param in query.Parameters)
{
cmd.Parameters.AddWithValue(param.Name, param.Value);
}
using (SqlDataAdapter dataAdapter = new SqlDataAdapter(cmd))
{
using (DataTable dataTable = new DataTable())
{
dataAdapter.Fill(dataTable);
return dataTable;
}
}
}
}
}
The code above "works" and the SQL statement from the ToTraceString() method is as follows:
SELECT [Extent1].[ColumnName1] AS [ColumnName1], [Extent1].[ColumnName2] AS [ColumnName2] FROM [dbo].[TableName] AS [Extent1]
Problem: The column names of the SQL statement (i.e. columnName1 and columnName2) do not correspond to the names of the properties of the objects (i.e. PropertyName1 and PropertyName2) that would be materialized if a ToList() or AsEnumerable() method was called on the query. This wouldn't be so bad if the SQL statement columns were in the same order as the anonymous object properties...but, this is not always the case. Somewhere (I guess inside of the IQueryable object) there must be a mapping between the SQL statement column names and the resulting anonymous object property names.
Does anyone know how to get at this mapping?

I've managed to find a solution to my problem:
First, you need the following code (from How does Entity Framework manage mapping query result to anonymous type?) which maps the positions of my anonymous object properties to the SQL statement column position:
public static Int32[] GetPropertyPositions(this ObjectQuery query)
{
// get private ObjectQueryState ObjectQuery._state;
// of actual type internal class
// System.Data.Objects.ELinq.ELinqQueryState
Object queryState = GetProperty(query, "QueryState");
AssertNonNullAndOfType(queryState, "System.Data.Objects.ELinq.ELinqQueryState");
// get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan;
// of actual type internal sealed class
// System.Data.Objects.Internal.ObjectQueryExecutionPlan
Object plan = GetField(queryState, "_cachedPlan");
AssertNonNullAndOfType(plan, "System.Data.Objects.Internal.ObjectQueryExecutionPlan");
// get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition;
// of actual type internal sealed class
// System.Data.EntityClient.EntityCommandDefinition
Object commandDefinition = GetField(plan, "CommandDefinition");
AssertNonNullAndOfType(commandDefinition, "System.Data.EntityClient.EntityCommandDefinition");
// get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator;
// of actual type private sealed class
// System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator
Object columnMapGenerator = GetField(commandDefinition, "_columnMapGenerator");
AssertNonNullAndOfType(columnMapGenerator, "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator");
// get private readonly ColumnMap ConstantColumnMapGenerator._columnMap;
// of actual type internal class
// System.Data.Query.InternalTrees.SimpleCollectionColumnMap
Object columnMap = GetField(columnMapGenerator, "_columnMap");
AssertNonNullAndOfType(columnMap, "System.Data.Query.InternalTrees.SimpleCollectionColumnMap");
// get internal ColumnMap CollectionColumnMap.Element;
// of actual type internal class
// System.Data.Query.InternalTrees.RecordColumnMap
Object columnMapElement = GetProperty(columnMap, "Element");
AssertNonNullAndOfType(columnMapElement, "System.Data.Query.InternalTrees.RecordColumnMap");
// get internal ColumnMap[] StructuredColumnMap.Properties;
// array of internal abstract class
// System.Data.Query.InternalTrees.ColumnMap
Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
AssertNonNullAndOfType(columnMapProperties, "System.Data.Query.InternalTrees.ColumnMap[]");
Int32 n = columnMapProperties.Length;
Int32[] propertyPositions = new Int32[n];
for (Int32 i = 0; i < n; ++i)
{
// get value at index i in array
// of actual type internal class
// System.Data.Query.InternalTrees.ScalarColumnMap
Object column = columnMapProperties.GetValue(i);
AssertNonNullAndOfType(column, "System.Data.Query.InternalTrees.ScalarColumnMap");
//string colName = (string)GetProp(column, "Name");
// can be used for more advanced bingings
// get internal int ScalarColumnMap.ColumnPos;
Object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");
propertyPositions[i] = (int)columnPositionOfAProperty;
}
return propertyPositions;
}
static object GetProperty(object obj, string propName)
{
PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null) throw EFChangedException();
return prop.GetValue(obj, new object[0]);
}
static object GetField(object obj, string fieldName)
{
FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null) throw EFChangedException();
return field.GetValue(obj);
}
static void AssertNonNullAndOfType(object obj, string fullName)
{
if (obj == null) throw EFChangedException();
string typeFullName = obj.GetType().FullName;
if (typeFullName != fullName) throw EFChangedException();
}
static InvalidOperationException EFChangedException()
{
return new InvalidOperationException("Entity Framework internals has changed, please review and fix reflection code");
}
Then I can modify the EntityToDatatable method as follows:
public static DataTable EntityToDatatable(this IQueryable query)
{
SqlConnection sqlConnection = null;
SqlCommand sqlCommand = null;
SqlDataAdapter sqlDataAdapter = null;
DataTable dataTable = null;
try
{
ObjectQuery objectQuery = (query as ObjectQuery);
ObjectContext objectContext = objectQuery.Context;
EntityConnection entityConnection = (objectContext.Connection as EntityConnection);
sqlConnection = new SqlConnection(entityConnection.StoreConnection.ConnectionString);
sqlCommand = new SqlCommand(objectQuery.ToTraceString(), sqlConnection);
foreach (var parameter in objectQuery.Parameters)
{
sqlCommand.Parameters.AddWithValue(parameter.Name, parameter.Value);
}
sqlDataAdapter = new SqlDataAdapter(sqlCommand);
dataTable = new DataTable();
sqlDataAdapter.Fill(dataTable);
// Get the mapping between the object property position and
// the SQL statment column position.
Int32[] propertyPositions = objectQuery.GetPropertyPositions();
// Create a column name to column position (ordinal) lookup.
Dictionary<String, Int32> mapColumnNameToColumnPosition = new Dictionary<string, int>();
// Populate the lookup.
for (Int32 i = 0; i < propertyPositions.Length; ++i)
{
mapColumnNameToColumnPosition.Add(dataTable.Columns[propertyPositions[i]].ColumnName, i);
}
// Get the object's property information.
PropertyInfo[] pi = query.GetType().GetGenericArguments()[0].GetProperties();
// Iterate through the lookup and change the position of the datatable columns.
// The order of the datatable columns will now correspond to the order of the object
// properties.
foreach (var map in mapColumnNameToColumnPosition)
{
// Change the column position.
dataTable.Columns[map.Key].SetOrdinal(map.Value);
// Change the column name.
dataTable.Columns[map.Key].ColumnName = pi[map.Value].Name;
}
return dataTable;
}
catch (Exception ex)
{
// Something went wrong and we're going to raise an exception...we
// might as well dispose of the datatable if it exists because it's
// not going to be used.
if (dataTable != null) dataTable.Dispose();
throw new Exception("IQueryable to DataTable conversion error.", ex);
}
finally
{
// Do some cleanup on objects that are no longer needed.
if (sqlDataAdapter != null) sqlDataAdapter.Dispose();
if (sqlCommand != null) sqlCommand.Dispose();
if (sqlConnection != null) sqlConnection.Dispose();
}
}

Related

How do I add an EPTimecardDetail record to a timecard?

I’m writing a customization to add records to a timecard and I’m trying to create a new record to add to the timecard. Using the logic in T230 I’m creating a variable and I’m being told by the compiler that EPTimecardDetail cannot be found.
I’ve added using PX.Objects.EP and PX.Objects.PM but I figure that if TimeCardMaint can be found then EPTimecardDetail should be able be found as well. I’ve included my using code as well but I think I’m missing something else.
using System;
using System.Collections;
using PX.Data;
using PX.Data.BQL.Fluent;
using PX.Data.BQL;
using PX.Objects.CS;
using PX.Objects.PM;
using PX.Objects.EP;
using PX.Objects.CR;
using PX.Objects.AR;
using PX.Objects.CT;
using PX.Objects.GL.FinPeriods;
using PX.TM;
using System.Collections.Generic;
namespace TimecardImport
{
public class NLTimecardLineEntry : PXGraph<NLTimecardLineEntry>
{
private static void DoPopulateTimeCard(Int32 employeeID, DateTime startDate, NLTimecardLine record)
{
TimeCardMaint graph = PXGraph.CreateInstance<TimeCardMaint>();
Int32 cardWeekID = PXWeekSelector2Attribute.GetWeekID(graph, startDate);
//look for an employee timecard with the current weekID
EPTimeCard card = PXSelectReadonly<EPTimeCard,
Where<EPTimeCard.employeeID, Equal<Required<EPTimeCard.employeeID>>,
And<EPTimeCard.weekId, Equal<Required<EPTimeCard.weekId>>>>>.SelectWindowed(graph, 0, 1, employeeID, cardWeekID);
if (card == null) //if a card was not found, create one
{
card = (EPTimeCard)graph.Document.Cache.CreateInstance();
card.WeekID = cardWeekID;
card.EmployeeID = employeeID;
card = graph.Document.Insert(card);
}
//at this point card is the card that we're going to work with
var detailLine = (EPTimecardDetail)graph.Activities.Cache.CreateCopy(
graph.Activities.Insert());
//detailLine.SetValueExt<detailLine.Date_Date>(record, record.InDate);
//detailLine.EarningTypeID = "RG";
//detailLine = graph.Activities.Update(detailLine);
graph.Save.Press();
}
}}
The error I'm getting is "The type or namespace name 'EPTimecardDetail' could not be found (are you missing a using directive or an assembly reference?)".
EPTimecardDetail is defined within PX.Objects.EP so I'm not sure why I'm having a problem there. Or, perhaps this is not the way to add records to the Details tab of the Employee Time Card screen?
For the namespace issue you can declare using PX.Object.EP and refer to the type as TimeCardMaint.EPTimecardDetail
Or you can declare using static PX.Objects.EP.TimeCardMaint and refer to the type as EPTimecardDetail
For inserting the record check the source code in file TimeCardMaint.cs There are examples on how to insert this DAC record.
Make sure the fields used for SQL joins like OrigNoteID and RefNoteID have the proper value (non null).
This example is from the Correct action in TimeCardMaint:
[PXUIField(DisplayName = Messages.Correct)]
[PXButton(ImageKey = PX.Web.UI.Sprite.Main.Release)]
public virtual IEnumerable Correct(PXAdapter adapter)
{
if (Document.Current != null)
{
EPTimeCard source = GetLastCorrection(Document.Current);
if (source.IsReleased != true)
return new EPTimeCard[] { source };
EPTimeCard newCard = (EPTimeCard)Document.Cache.CreateInstance();
newCard.WeekID = source.WeekID;
newCard.OrigTimeCardCD = source.TimeCardCD;
newCard = Document.Insert(newCard);
newCard.EmployeeID = source.EmployeeID;
PXNoteAttribute.CopyNoteAndFiles(Document.Cache, source, Document.Cache, newCard, true, true);
bool failed = false;
Dictionary<string, TimeCardSummaryCopiedInfo> summaryDescriptions = new Dictionary<string, TimeCardSummaryCopiedInfo>();
foreach (EPTimeCardSummary summary in Summary.View.SelectMultiBound(new object[] { source }))
{
string key = GetSummaryKey(summary);
if (!summaryDescriptions.ContainsKey(key))
{
string note = PXNoteAttribute.GetNote(Summary.Cache, summary);
var info = new TimeCardSummaryCopiedInfo(summary.Description, note);
summaryDescriptions.Add(key, info);
}
}
foreach (EPTimecardDetail act in TimecardActivities.View.SelectMultiBound(new object[] { source }))
{
EPTimecardDetail newActivity = PXCache<EPTimecardDetail>.CreateCopy(act);
newActivity.Released = false;
newActivity.Billed = false;
newActivity.NoteID = null;
newActivity.TimeCardCD = null;
newActivity.TimeSheetCD = null;
newActivity.OrigNoteID = act.NoteID; //relation between the original activity and the corrected one.
newActivity.Date = act.Date;
newActivity.Billed = false;
newActivity.SummaryLineNbr = null;
newActivity.NoteID = null;
newActivity.ContractCD = null;
isCreateCorrectionFlag = true;
try
{
newActivity = Activities.Insert(newActivity);
}
catch (PXSetPropertyException ex)
{
failed = true;
Activities.Cache.RaiseExceptionHandling<EPTimecardDetail.summary>(act, act.Summary, new PXSetPropertyException(ex.MessageNoPrefix, PXErrorLevel.RowError));
continue;
}
newActivity.TrackTime = act.TrackTime; //copy as is.
isCreateCorrectionFlag = false;
newActivity.ApprovalStatus = ActivityStatusAttribute.Completed;
newActivity.RefNoteID = act.NoteID == act.RefNoteID ? newActivity.NoteID : act.RefNoteID;
newActivity.ContractCD = act.ContractCD;
PXNoteAttribute.CopyNoteAndFiles(Activities.Cache, act, Activities.Cache, newActivity);
Activities.Cache.SetValue<EPTimecardDetail.isCorrected>(act, true);
Activities.Cache.SetStatus(act, PXEntryStatus.Updated);
}
if (failed)
{
throw new PXException(Messages.FailedToCreateCorrectionTC);
}
foreach (EPTimeCardItem item in Items.View.SelectMultiBound(new object[] { source }))
{
EPTimeCardItem record = Items.Insert();
record.ProjectID = item.ProjectID;
record.TaskID = item.TaskID;
record.Description = item.Description;
record.InventoryID = item.InventoryID;
record.CostCodeID = item.CostCodeID;
record.UOM = item.UOM;
record.Mon = item.Mon;
record.Tue = item.Tue;
record.Wed = item.Wed;
record.Thu = item.Thu;
record.Fri = item.Fri;
record.Sat = item.Sat;
record.Sun = item.Sun;
record.OrigLineNbr = item.LineNbr;//relation between the original activity and the corrected one.
}
foreach (EPTimeCardSummary summary in Summary.Select())
{
string key = GetSummaryKey(summary);
if (summaryDescriptions.ContainsKey(key))
{
PXNoteAttribute.SetNote(Summary.Cache, summary, summaryDescriptions[key].Note);
Summary.Cache.SetValue<EPTimeCardSummary.description>(summary, summaryDescriptions[key].Description);
}
}
return new EPTimeCard[] { newCard };
}
return adapter.Get();
}

How to get Opportunity Relations in Sales Order

In Opportunity screen, the definition of the data view for Relations is simply :
public CRRelationsList<CROpportunity.noteID> Relations;
When a Sales Order is raised from the Opportunity. I'd like to display the Relations defined from the source Opporunity in another tab. And I'm just struggling how to write the the data view and pass the Opportunity noteid.
public CRRelationsList<???>Relations;
Thanks !
The generic type in dataviews often resolve to the current record.
In CRRelationsList class the generic type is named TNoteField:
public class CRRelationsList<TNoteField> : PXSelect<CRRelation>
where TNoteField : IBqlField
ssuming the dataview is declared as CRRelationsList<CROpportunity.noteID>.
The generic type value will be resolved like this Caches[typeof(CROpportunity)].Current.NoteID.
protected virtual void CRRelation_RefNoteID_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
// Get a cache object of type CROpportunity
var refCache = sender.Graph.Caches[BqlCommand.GetItemType(typeof(TNoteField))];
// Get the NoteID field value of the current CROpportunity object
e.NewValue = refCache.GetValue(refCache.Current, typeof(TNoteField).Name);
}
So to set DAC.Field of CRelationsList<DAC.field> you would do:
// In a graph extension (PXGraphExtension)
Base.Caches[typeof(DAC)].Current.Fied = ???;
// Or in graph (PXGraph)
Caches[typeof(DAC)].Current.Fied = ???;
If current DAC object is null you need to insert a record in a dataview or directly in the cache object.
I'm not sure re-using CRRelationsList list is the best approach if you want to simply display records because it does much more than that. It should be possible to extract the select request out of it and directly substitute the TNoteField value:
private static PXSelectDelegate GetHandler()
{
return () =>
{
var command = new Select2<CRRelation,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<CRRelation.entityID>>,
LeftJoin<Contact,
On<Contact.contactID, Equal<Switch<Case<Where<BAccount.type, Equal<BAccountType.employeeType>>, BAccount.defContactID>, CRRelation.contactID>>>,
LeftJoin<Users, On<Users.pKID, Equal<Contact.userID>>>>>,
Where<CRRelation.refNoteID, Equal<Current<TNoteField>>>>();
var startRow = PXView.StartRow;
int totalRows = 0;
var list = new PXView(PXView.CurrentGraph, false, command).
Select(null, null, PXView.Searches, PXView.SortColumns, PXView.Descendings, PXView.Filters,
ref startRow, PXView.MaximumRows, ref totalRows);
PXView.StartRow = 0;
foreach (PXResult<CRRelation, BAccount, Contact, Users> row in list)
{
var relation = (CRRelation)row[typeof(CRRelation)];
var account = (BAccount)row[typeof(BAccount)];
relation.Name = account.AcctName;
relation.EntityCD = account.AcctCD;
var contact = (Contact)row[typeof(Contact)];
if (contact.ContactID == null && relation.ContactID != null &&
account.Type != BAccountType.EmployeeType)
{
var directContact = (Contact)PXSelect<Contact>.
Search<Contact.contactID>(PXView.CurrentGraph, relation.ContactID);
if (directContact != null) contact = directContact;
}
relation.Email = contact.EMail;
var user = (Users)row[typeof(Users)];
if (account.Type != BAccountType.EmployeeType)
relation.ContactName = contact.DisplayName;
else
{
if (string.IsNullOrEmpty(relation.Name))
relation.Name = user.FullName;
if (string.IsNullOrEmpty(relation.Email))
relation.Email = user.Email;
}
}
return list;
};
}

Casting on run time using implicit con version

I have the following code which copies property values from one object to another objects by matching their property names:
public static void CopyProperties(object source, object target,bool caseSenstive=true)
{
PropertyInfo[] targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo tp in targetProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(p => p.Name == tp.Name);
if (sourceProperty == null && !caseSenstive)
{
sourceProperty = sourceProperties.FirstOrDefault(p => p.Name.ToUpper() == tp.Name.ToUpper());
}
// If source doesn't have this property, go for next one.
if(sourceProperty ==null)
{
continue;
}
// If target property is not writable then we can not set it;
// If source property is not readable then cannot check it's value
if (!tp.CanWrite || !sourceProperty.CanRead)
{
continue;
}
MethodInfo mget = sourceProperty.GetGetMethod(false);
MethodInfo mset = tp.GetSetMethod(false);
// Get and set methods have to be public
if (mget == null)
{
continue;
}
if (mset == null)
{
continue;
}
var sourcevalue = sourceProperty.GetValue(source, null);
tp.SetValue(target, sourcevalue, null);
}
}
This is working well when the type of properties on target and source are the same. But when there is a need for casting, the code doesn't work.
For example, I have the following object:
class MyDateTime
{
public static implicit operator DateTime?(MyDateTime myDateTime)
{
return myDateTime.DateTime;
}
public static implicit operator DateTime(MyDateTime myDateTime)
{
if (myDateTime.DateTime.HasValue)
{
return myDateTime.DateTime.Value;
}
else
{
return System.DateTime.MinValue;
}
}
public static implicit operator MyDateTime(DateTime? dateTime)
{
return FromDateTime(dateTime);
}
public static implicit operator MyDateTime(DateTime dateTime)
{
return FromDateTime(dateTime);
}
}
If I do the following, the implicit cast is called and everything works well:
MyDateTime x= DateTime.Now;
But when I have a two objects that one of them has a DateTime and the other has MyDateTime, and I am using the above code to copy properties from one object to other, it doesn't and generate an error saying that DateTime can not converted to MyTimeDate.
How can I fix this problem?
One ghastly approach which should work is to mix dynamic and reflection:
private static T ConvertValue<T>(dynamic value)
{
return value; // This will perform conversion automatically
}
Then:
var sourceValue = sourceProperty.GetValue(source, null);
if (sourceProperty.PropertyType != tp.PropertyType)
{
var method = typeof(PropertyCopier).GetMethod("ConvertValue",
BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(new[] { tp.PropertyType };
sourceValue = method.Invoke(null, new[] { sourceValue });
}
tp.SetValue(target, sourceValue, null);
We need to use reflection to invoke the generic method with the right type argument, but dynamic typing will use the right conversion operator for you.
Oh, and one final request: please don't include my name anywhere near this code, whether it's in comments, commit logs. Aargh.

Check if a List Column Exists using SharePoint Client Object Model?

Using the Client Object Model (C#) in SharePoint 2010, how can I determine if a specified column (field) name exists in a given List?
Thanks, MagicAndi.
Just found this while searching for the same thing, but it looks like Sharepoint 2010 has something built in for this, at least for the Server model: list.Fields.ContainsField("fieldName");
Not sure if it exists for Client side though. Figured it would be a good place to store this information however.
Server Object Model
string siteUrl = "http://mysite";
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["my forum"];
for (int i = 0; i < list.Fields.Count; i++)
{
if (list.Fields[i].Title == "xyz")
{
-
-
}
}
}
}
Client Object Model
string siteUrl = "http://MyServer/sites/MySiteCollection";
ClientContext clientContext = new ClientContext(siteUrl);
SP.List List = clientContext.Web.Lists.GetByTitle("my forum");
for (int i = 0; i < list.Fields.Count; i++)
{
if (list.Fields[i].Title == "xyz")
{
-
-
}
}
The following method demonstrates how to determine whether a specified column exists in a List using CSOM:
static class FieldCollectionExtensions
{
public static bool ContainsField(this List list,string fieldName)
{
var ctx = list.Context;
var result = ctx.LoadQuery(list.Fields.Where(f => f.InternalName == fieldName));
ctx.ExecuteQuery();
return result.Any();
}
}
Usage
using(var ctx = new ClientContext(webUrl))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
if(list.ContainsField("Title")){
//...
}
}
Here's an extension code (CSOM) for sharepoint list
public static bool DoesFieldExist(this List list, ClientContext clientContext, string internalFieldname)
{
bool exists = false;
clientContext.Load(list.Fields, fCol => fCol.Include(
f => f.InternalName
).Where(field => field.InternalName == internalFieldname));
clientContext.ExecuteQuery();
if (list.Fields != null && list.Fields.Count > 0)
{
exists = true;
}
return exists;
}
usage
List targetList = this.Context.Web.Lists.GetById(<ListID>);
targetList.DoesFieldExist(<ClientContext>, <Field internal Name>)
enjoy :)
I ended up retrieving the details of the list's fields prior to my operation, and saving them in a generic list of structs (containing details of each field). I then query this (generic) list to see if the current field actually exists in the given (SharePoint) list.
// Retrieve detail sof all fields in specified list
using (ClientContext clientContext = new ClientContext(SharePointSiteUrl))
{
List list = clientContext.Web.Lists.GetByTitle(listName);
_listFieldDetails = new List<SPFieldDetails>();
// get fields name and their types
ClientObjectPrototype allFields = list.Fields.RetrieveItems();
allFields.Retrieve( FieldPropertyNames.Title,
FieldPropertyNames.InternalName,
FieldPropertyNames.FieldTypeKind,
FieldPropertyNames.Id,
FieldPropertyNames.ReadOnlyField);
clientContext.ExecuteQuery();
foreach (Field field in list.Fields)
{
SPFieldDetails fieldDetails = new SPFieldDetails();
fieldDetails.Title = field.Title;
fieldDetails.InternalName = field.InternalName;
fieldDetails.Type = field.FieldTypeKind;
fieldDetails.ID = field.Id;
fieldDetails.ReadOnly = field.ReadOnlyField;
listFieldDetails.Add(fieldDetails);
}
}
// Check if field name exists
_listFieldDetails.Exists(field => field.Title == fieldName);
// Struct to hold details of the field
public struct SPFieldDetails
{
public string Title { get; set; }
public string InternalName { get; set; }
public Guid ID { get; set; }
public FieldType Type { get; set; }
public bool ReadOnly { get; set; }
}
Some good answers above. I personally used this one:
List list = ctx.Web.Lists.GetByTitle("Some list");
FieldCollection fields = list.Fields;
IEnumerable<Field> fieldsColl = ctx.LoadQuery(fields.Include(f => f.InternalName));
ctx.ExecuteQuery();
bool fieldMissing = fieldsColl.Any(f => f.InternalName != "Internal_Name");
You can also use 'Where' after Include method and check if returned collection/field is null. It's about personal preference, because both options are querying on client side.
I prefer the SharePoint Plus Library as it is really clean:
http://aymkdn.github.io/SharepointPlus/symbols/%24SP%28%29.list.html
$SP().list("My List").get({
fields:"Title",
where:"Author = '[Me]'"
},function getData(row) {
console.log(row[0].getAttribute("Title"));
});
You could setup a for loop to loop through the row and check if the column you're looking for exists.
A cut down and simplified version of Mitya's extension method:
public static bool FieldExists(this List list, string internalFieldname)
{
using (ClientContext clientContext = list.Context as ClientContext)
{
clientContext.Load(list.Fields, fCol => fCol.Include(
f => f.InternalName
).Where(field => field.InternalName == internalFieldname));
clientContext.ExecuteQuery();
return (list.Fields != null) && (list.Fields.Count > 0);
}
}
There's no need to pass in a separate client context parameter when you can already use the context that comes in with the list.
to much code use this
load Fields first then
bool exists= clientContext2.Site.RootWeb.Fields.Any(o => o.Id.ToString() == a.Id.ToString());

Anonymous type and getting values out side of method scope

I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}

Resources