Anonymous type and getting values out side of method scope - c#-4.0

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

Related

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;
};
}

Dynamics CRM SDK - IN operator for linq with OrganizationServiceContext

I'm using my OrganizationServiceContext implementation generated by the svcutil to retrieve entities from CRM:
context.new_productSet.First(p => p.new_name == "Product 1");
Is it possible to retrieve multiple entities with different attribute values at once - (smth like IN operator in SQL)?
Example: I would like to retrieve multiple products ("Product 1", "Product 2", ...) with a single call. The list of product names is dynamic, stored in an array called productNames.
No, you can't. CRM LINQ provider only allows variables to appear on the left side of expressions, while the right side must contain constants.
i.e.
Product.Where(e => e.Name == desiredName)
Is not supported and won't work (it will complain about using a variable on the right side of the comparison).
If you cannot avoid this kind of query, you have to .ToList() data first (this can lead to a huge result set and will probably turn up to be unconceivably slow):
Product.ToList().Where(e => e.Name == desiredName)
This will work, because now the .Where() is being applied on a List<> instead.
Another approach (I don't have data about performance, though) would be to create many queries, basically fetching the records one at a time:
// ... this is going to be a nightmare ... don't do it ...
var entities = new List<Product>();
entities.Add(Product.Where(e => e.Name == "Product 1"));
entities.Add(Product.Where(e => e.Name == "Product 2"));
Or use a QueryExpression like this (my personal favourite, because I always go late-bound)
var desiredNames = new string[]{"Product 1", "Product 2"};
var filter = new FilterExpression(LogicalOperator.And)
{
Conditions =
{
new ConditionExpression("name", ConditionOperator.In, desiredNames)
}
};
var query = new QueryExpression(Product.EntityLogicalName)
{
ColumnSet = new ColumnSet(true),
Criteria = filter
};
var records = service.RetrieveMultiple(query).Entities;
If combining Linq and Lambda expression is ok, it can be done. First you need to create an extension method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Kipon.Dynamics.Extensions.IQueryable
{
public static class Methods
{
public static IQueryable<TSource> WhereIn<TSource, TValue>(this IQueryable<TSource> source, Expression<Func<TSource, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == source) { throw new ArgumentNullException("source"); }
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
var equalExpressions = new List<BinaryExpression>();
foreach (var value in values)
{
var equalsExpression = Expression.Equal(valueSelector.Body, Expression.Constant(value));
equalExpressions.Add(equalsExpression);
}
ParameterExpression p = valueSelector.Parameters.Single();
var combined = equalExpressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
var combinedLambda = Expression.Lambda<Func<TSource, bool>>(combined, p);
return source.Where(combinedLambda);
}
}
}
With this method in place, you can now use it against your context. First remember to import the namespace of the extension to make the method available on IQueryable:
using System.Linq;
using Kipon.Dynamics.Extensions.IQueryable;
public class MyClass
{
void myQueryMethod(CrmContext ctx, Guid[] contacts)
{
var accounts = (from a in ctx.accountSet.WhereIn(ac => ac.primarycontactid.id,contacts)
where a.name != null
select a).toArray();
}
}
There is no way you can hook into the Dynamics 365 Linq expression compiler, as far as I know, but the above code will execute in one request against the CRM, and take advantage
of the fact that you do not need to consider paging and more when working with Linq.
As you can see, there whereIn clause is added with a lambda style expression, where the rest of the query is using the Linq style.
When using QueryExpression, we can add condtionexpression for where clause. ConditionExpression takes a ConditionOperator enumerator, and we can use ConditionOperator.In. Below is how you initiate a conidtionExpression with an “In” operator, the third argument can be an array or collection.
ConditionExpression ce = new ConditionExpression("EntityName",
ConditionOperator.In, collectionObject);
Please see below for further explanation.
http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.query.conditionexpression.conditionexpression.aspx
I do not know how to do this with Linq, as far as I know it is not possible.
It can be done with Query Expressions:
String[] productNames = new[] { "test1", "test2" };
QueryExpression products = new QueryExpression(Product.EntityLogicalName);
products.ColumnSet = new ColumnSet("name", "new_att1", "new_att2"); // fields to get
products.Criteria.AddCondition("name", ConditionOperator.In,
productNames.Cast<Object>().ToArray()); // filter by array
EntityCollection res = service.RetrieveMultiple(products);
IEnumerable<Product> opportunities = res.Entities
.Select(product => product.ToEntity<Product>()); // you can use Linq again from here

Dynamics CRM - Accessing Custom Product Option Value

Is there a way to programmatically access the Label & Value fields that has been created as a custom Field in MS CRM Dynamics please?
I have added a custom field called "new_producttypesubcode" which, for example, has 2 options, Trophy = 1000000 and Kit = 10000001.
I am writing an import utility that mirrors products between the customers website and their CRM and I want to get a list of all possible product options in the CRM to see if they are matched in the website.
So, in essence I want to...
get the list of possible new_producttypesubcodes and their corresponding values.
Iterate through the product variants in the website.
if the product variant name matches any name in the list of new_producttypecodes then add the value 1000000
So, if I find a product added to the website and its marked as a "Trophy" and "Trophy" exists in the CRM then new OptionSetValue(100000001)
I hope that makes sense...
Thanks
This function retrieves a dictionary of possible values localised to the current user. Taken from: CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options).
static Dictionary<String, int> GetNumericValues(IOrganizationService service, String entity, String attribute)
{
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = entity,
LogicalName = attribute,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response = (RetrieveAttributeResponse)service.Execute(request);
switch (response.AttributeMetadata.AttributeType)
{
case AttributeTypeCode.Picklist:
case AttributeTypeCode.State:
case AttributeTypeCode.Status:
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options
.ToDictionary(key => key.Label.UserLocalizedLabel.Label, option => option.Value.Value);
case AttributeTypeCode.Boolean:
Dictionary<String, int> values = new Dictionary<String, int>();
BooleanOptionSetMetadata metaData = ((BooleanAttributeMetadata)response.AttributeMetadata).OptionSet;
values[metaData.TrueOption.Label.UserLocalizedLabel.Label] = metaData.TrueOption.Value.Value;
values[metaData.FalseOption.Label.UserLocalizedLabel.Label] = metaData.FalseOption.Value.Value;
return values;
default:
throw new ArgumentOutOfRangeException();
}
}
So you would then need to do something like:
Dictionary<String, int> values = GetNumericValues(proxy, "your_entity", "new_producttypesubcode");
if(values.ContainsKey("Trophy"))
{
//Do something with the value
OptionSetValue optionSetValue = values["Trophy"];
int value = optionSetValue.Value;
}
Yes, that data is all stored in the metadata for an attribute (SDK article). You have to retrieve the entity metadata for the entity and then find the attribute in the list. Then cast that attribute to a PicklistAttributeMetadata object and it will contain a list of options. I would mention that typically retrieving Metadata from CRM is an expensive operation, so think about caching.
private static OptionSetMetadata RetrieveOptionSet(IOrganizationService orgService,
string entityName, string attributeName)
{
var entityResponse = (RetrieveEntityResponse)orgService.Execute(
new RetrieveEntityRequest
{ LogicalName = entityName, EntityFilters = EntityFilters.Attributes });
var entityMetadata = entityResponse.EntityMetadata;
for (int i = 0; i < entityMetadata.Attributes.Length; i++)
{
if (attributeName.Equals(entityMetadata.Attributes[i].LogicalName))
{
if (entityMetadata.Attributes[i].AttributeType.Value ==
AttributeTypeCode.Picklist)
{
var attributeMD = (PicklistAttributeMetadata)
entityMetadata.Attributes[i];
return attributeMD.OptionSet;
}
}
}
return null;
}
Here is how to write the options to the console using the above call.
var optionSetMD = RetrieveOptionSet(orgService, "account", "accountcategorycode");
var options = optionSetMD.Options;
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine("Local Label: {0}. Value: {1}",
options[i].Label.UserLocalizedLabel.Label,
options[i].Value.HasValue ? options[i].Value.Value.ToString() : "null");
}
I believe this works for global option set attributes as well, but if you know it is a global option set there is a different message for it that would probably a bit more efficient (SDK article).

Return a set of objects from a class

I have a method that adds a new item to an EF table, then queries back the table to return a subset of the table. It needs to return to the caller a set of "rows", each of which is a set of columns. I'm not sure how to do this. I have some code, but I think it's wrong. I don't want to return ONE row, I want to return zero or more rows. I'm not sure what DataType to use... [qryCurrentTSApproval is an EF object, referring to a small view in SS. tblTimesheetEventlog is also an EF object, referring to the underlying table]
Ideas?
private qryCurrentTSApproval LogApprovalEvents(int TSID, int EventType)
{
using (CPASEntities ctx = new CPASEntities())
{
tblTimesheetEventLog el = new tblTimesheetEventLog();
el.TSID = TSID;
el.TSEventType = EventType;
el.TSEUserName = (string)Session["strShortUserName"];
el.TSEventDateTime = DateTime.Now;
ctx.tblTimesheetEventLogs.AddObject(el);
ctx.AcceptAllChanges();
var e = (from x in ctx.qryCurrentTSApprovals
where x.TSID == TSID
select x);
return (qryCurrentTSApproval)e;
}
}
Change your method return type to a collection of qryCurrentTSApproval
private List<qryCurrentTSApproval> LogApprovalEvents(int TSID, int EventType)
{
using (CPASEntities ctx = new CPASEntities())
{
// some other existing code here
var itemList = (from x in ctx.qryCurrentTSApprovals
where x.TSID == TSID
select x).ToList();
return itemList;
}
}

Entity Framework - IQueryable to DataTable

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();
}
}

Resources