Converting Hand Written DI to Windsor Provided DI - c#-4.0

For the past six or seven months I have been doing DI in some of my components as result they have grown to become little bit of complicated. In the past I have been creating Object graphs with hand written Factories. Since it is becoming unmanageable I am trying to move that code to Framework dependent DI(by code and not by some XML files). I am posting my Code as well as issues I am stuck with.
Here is my composition layer (it is big, so bear with me :) ):
IAgentFactory GetAgentFactory()
{
string errorMessage;
IDictionary<AgentType, ServiceParameters> agentFactoryPrerequisite = new Dictionary<AgentType, ServiceParameters>();
string restResponseHeaderStatus = MyConfigurationProject.GetConfigValue("RestResponseHeaderStatus", out errorMessage);
var service1Parameters = new ServiceParameters();
service1Parameters.BindingName = MyConfigurationProject.GetConfigValue("Service1WebHttpBindingConfiguration", out errorMessage).ToString();
service1Parameters.HeaderPassword = MyConfigurationProject.GetConfigValue("Service1HeaderPassword", out errorMessage).ToString();
service1Parameters.HeaderUserName = MyConfigurationProject.GetConfigValue("Service1HeaderUserName", out errorMessage).ToString();
service1Parameters.ResponseHeaderStatus = restResponseHeaderStatus;
service1Parameters.ServicePassword = MyConfigurationProject.GetConfigValue("Service1ServicePassword", out errorMessage).ToString();
service1Parameters.ServiceUrl = MyConfigurationProject.GetConfigValue("Service1URL", out errorMessage).ToString();
service1Parameters.ServiceUserName = MyConfigurationProject.GetConfigValue("Service1ServiceUserName", out errorMessage).ToString();
agentFactoryPrerequisite.Add(new KeyValuePair<AgentType, ServiceParameters>(AgentType.Service1, service1Parameters));
var agentFactory = new AgentFactory(agentFactoryPrerequisite);
return agentFactory;
}
protected DatalayerSettings GetDataLayerSettings()
{
var datalayerSettings = new DatalayerSettings();
datalayerSettings.ConnectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
datalayerSettings.MySchemaName = ConfigurationManager.AppSettings["MyDatabaseSchema"];
datalayerSettings.UpdatingUser = "Admin";
return datalayerSettings;
}
PostgersDAFactory GetPostGresDaFactory()
{
var datalayerSettings = GetDataLayerSettings();
return new PostgersDAFactory(datalayerSettings, "MyAssembly.PostgresDA", "MyDifferentAssembly.CommonDatalayer", "MyServiceLogPath");
}
public class PostgersDAFactory
{
readonly DatalayerSettings _datalayerSettings;
readonly string _assemblyName;
readonly string _logPath;
readonly string _mySecondAssemblyName;
public PostgersDAFactory(DatalayerSettings datalayerSettings, string assemblyName, string mySecondAssemblyName, string logPath)
{
_datalayerSettings = datalayerSettings;
_assemblyName = assemblyName;
_logPath = logPath;
_commonDaAssemblyName = commonDaAssemblyName;
}
public IDA1 GetDA1Instance()
{
var type1 = Type.GetType("MyAssembly.PostgresDA.ClassRealisingImplementation_For_DA1," + _assemblyName);
return (IDA1)Activator.CreateInstance(type1, _datalayerSettings, _logPath);
}
public IDA2 GetDA2Instance()
{
var type1 = Type.GetType("MyAssembly.PostgresDA.ClassRealisingImplementation_For_DA2," + _assemblyName);
return (IDA2)Activator.CreateInstance(type1, _datalayerSettings);
}
public IDA3 GetDA3Instance()
{
var type1 = Type.GetType("MyAssembly2.ClassRealisingImplementation_For_DA3," + _commonDaAssemblyName);
return (IDA3)Activator.CreateInstance(type1, _datalayerSettings);
}
}
public BaseFileHandler GetFileHandler(FileProvider fileprovider, MockedServiceCalculator mockedServicecalculator = null)
{
string errorMessage;
var postgresFactory = GetPostGresDaFactory();
var Da1Instance = postgresFactory.GetDA1Instance();
var fileSyncBusiness = new FileSyncBusiness(Da1Instance);
var interfaceConfiguratonParameters = fileSyncBusiness.GetInterfaceConfigurationParameters();
var servicePointDetailsSettings = new ServicePointDetailsSettings();
var nullDate = new DateTime(2099, 1, 1);
CommonValidations commonValidations;
if (mockedServicecalculator == null)
{
commonValidations = GetStubbedCommonValidations(nullDate);
}
else
{
commonValidations = GetCommonValidations_WithMockedServiceCalculator(nullDate, mockedServicecalculator);
}
switch (fileprovider)
{
case FileProvider.Type1:
var type1Adapter = new Type1Adaptor(false, nullDate);
servicePointDetailsSettings = GetUtiltaParameters(interfaceConfiguratonParameters);
return new Type1FileHandler(servicePointDetailsSettings, fileSyncBusiness, commonValidations, type1Adapter);
case FileProvider.Type2:
var type2Adapter = new Type2Adaptor(true, nullDate);
servicePointDetailsSettings.ApplicableParameters = MyApplicationCommonMethods.ConvertConfigurationTableToDictonary(interfaceConfiguratonParameters, "applicableintype2");
servicePointDetailsSettings.BadFileLocation = MyConfigurationProject.GetConfigValue("Type2BadFileLocation", out errorMessage);
servicePointDetailsSettings.DateFormat = MyConfigurationProject.GetConfigValue("Type2DateFormat", out errorMessage);
servicePointDetailsSettings.FailureFileLocation = MyConfigurationProject.GetConfigValue("Type2FailureFile", out errorMessage);
servicePointDetailsSettings.LogFileName = "Type2LogFile";
servicePointDetailsSettings.LogPath = MyConfigurationProject.GetConfigValue("Type2ErrorLog", out errorMessage);
servicePointDetailsSettings.MandatoryParameters = MyApplicationCommonMethods.GetDictonaryForMandatoryParameters(interfaceConfiguratonParameters, "applicableintype2", "mandatoryintype2");
servicePointDetailsSettings.SourceFileLocation = MyConfigurationProject.GetConfigValue("type2FileLocation", out errorMessage);
servicePointDetailsSettings.SuccessFileLocation = MyConfigurationProject.GetConfigValue("type2SuccessFile", out errorMessage);
servicePointDetailsSettings.TargetFileExtension = MyConfigurationProject.GetConfigValue("type2SupportedFileType", out errorMessage);
servicePointDetailsSettings.Type2RecordTag = MyConfigurationProject.GetConfigValue("MyApplicationtype2RecordTag", out errorMessage);
return new Type2FileHandler(servicePointDetailsSettings, fileSyncBusiness, commonValidations, type2Adapter);
default:
throw new NotImplementedException("FileProvider type: " + Convert.ToInt32(fileprovider) + " is not implemented");
}
}
}
While moving towards Windsor, I am facing several issues, as I have never used this product, it seems it is very complicated.
Issues:
How to pass parameters to object when they have parameterised
constructors?
I know there is a better way to write this "PostgersDAFactory"
class, but simply don't know.
There are some Factory methods Such as GetAgentFactory(), which are
dependent on some static method of other project, which in turn
gives me configuration values(I ahd to store them in the database),
another method GetDataLayerSettings is dependent on app config as
well as some static string.
I am likely to change parameter names in my classes in order to
promote readability, so how to turn on the logging for Windsor?
Finally another complicated method GetFileHandler, has some logic
(switch case).
I have tried going on there website but I found it totally difficult to digest information, there API is huge and learning curve seems to be mammoth.
Note: I had to change the variable names due to security reasons.

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

ServiceStack.Ormlite single poco map to many tables

I know that Servicestack.Ormlite is setup to be a 1:1 mapping between poco and database table. I have a situation where I will have groups of tables that are of the same structure and they are created as necessary. I am trying to find a way to be able to do something where I can continue to use the IDbConnection and specify the table name in CRUD operations.
Something like
using(var db = _conn.OpenDbConnection()){
db.SaveAll(objList, "DIFFERENT_TABLE");
}
I was easily able to work around to make creating and deleting the tables. I am hoping that I can use of the ExpressionVisitor or something else to help change the table name before it is executed. One of the requirements of the project is that it be database agnostic, which is why I am trying to not manually write out the SQL.
Solutions
Here are a couple of functions that I ended up creating if anyone out there wants some more examples.
public static List<T> SelectTable<T>(this IDbConnection conn, string tableName) {
var stmt = ModelDefinition<T>.Definition.SqlSelectAllFromTable;
stmt = stmt.Replace(ModelDefinition<T>.Definition.Name, tableName.FmtTable());
return conn.Select<T>(stmt);
}
public static List<T> SelectTableFmt<T>(this IDbConnection conn, string tableName, string sqlFilter,
params object[] filterParams) {
var stmt = conn.GetDialectProvider().ToSelectStatement(typeof (T), sqlFilter, filterParams);
stmt = stmt.Replace(ModelDefinition<T>.Definition.Name, tableName.FmtTable());
return conn.Select<T>(stmt);
}
public static void InsertTable<T>(this IDbConnection conn, T obj, string tablename) {
var stmt = conn.GetDialectProvider().ToInsertRowStatement(null, obj);
stmt = stmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(stmt);
}
public static int SaveAll<T>(this IDbConnection conn, string tablename, IEnumerable<T> objs) {
var saveRows = objs.ToList();
var firstRow = saveRows.FirstOrDefault();
if (Equals(firstRow, default(T))) return 0;
var defaultIdValue = firstRow.GetId().GetType().GetDefaultValue();
var idMap = defaultIdValue != null
? saveRows.Where(x => !defaultIdValue.Equals(x.GetId())).ToSafeDictionary(x => x.GetId())
: saveRows.Where(x => x.GetId() != null).ToSafeDictionary(x => x.GetId());
var existingRowsMap = conn.SelectByIds<T>(tablename, idMap.Keys).ToDictionary(x => x.GetId());
var modelDef = ModelDefinition<T>.Definition;
var dialectProvider = conn.GetDialectProvider();
var rowsAdded = 0;
using (var dbTrans = conn.OpenTransaction()) {
foreach (var obj in saveRows) {
var id = obj.GetId();
if (id != defaultIdValue && existingRowsMap.ContainsKey(id)) {
var updStmt = dialectProvider.ToUpdateRowStatement(obj);
updStmt = updStmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(updStmt);
}
else {
if (modelDef.HasAutoIncrementId) {}
var stmt = dialectProvider.ToInsertRowStatement(null, obj);
stmt = stmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(stmt);
rowsAdded++;
}
}
dbTrans.Commit();
}
return rowsAdded;
}
OrmLite supports specifying the table name for Update and Delete operations. Unfortunately the examples in the readme here have yet to be updated. This is the required format:
UPDATE:
db.UpdateFmt(table: "Person", set: "FirstName = {0}".Fmt("JJ"), where: "LastName = {0}".Fmt("Hendrix"));
DELETE:
db.DeleteFmt(table: "Person", where: "Age = {0}".Fmt(27));
The methods you need can be found here. You should be able to use .Exec to handle reading and insert operations.

Issue with SqlScalar<T> and SqlList<T> when calling stored procedure with parameters

The new API for Servicestack.OrmLite dictates that when calling fx a stored procedure you should use either SqlScalar or SqlList like this:
List<Poco> results = db.SqlList<Poco>("EXEC GetAnalyticsForWeek 1");
List<Poco> results = db.SqlList<Poco>("EXEC GetAnalyticsForWeek #weekNo", new { weekNo = 1 });
List<int> results = db.SqlList<int>("EXEC GetTotalsForWeek 1");
List<int> results = db.SqlList<int>("EXEC GetTotalsForWeek #weekNo", new { weekNo = 1 });
However the named parameters doesn't work. You HAVE to respect the order of the parameters in the SP. I think it is because the SP is executed using CommandType=CommandType.Text instead of CommandType.StoredProcedure, and the parameters are added as dbCmd.Parameters.Add(). It seems that because the CommandType is Text it expects the parameters to be added in the SQL querystring, and not as Parameters.Add(), because it ignores the naming.
An example:
CREATE PROCEDURE [dbo].[sproc_WS_SelectScanFeedScanRecords]
#JobNo int = 0
,#SyncStatus int = -1
AS
BEGIN
SET NOCOUNT ON;
SELECT
FSR.ScanId
, FSR.JobNo
, FSR.BatchNo
, FSR.BagNo
, FSR.ScanType
, FSR.ScanDate
, FSR.ScanTime
, FSR.ScanStatus
, FSR.SyncStatus
, FSR.JobId
FROM dbo.SCAN_FeedScanRecords FSR
WHERE ((FSR.JobNo = #JobNo) OR (#JobNo = 0) OR (ISNULL(#JobNo,1) = 1))
AND ((FSR.SyncStatus = #SyncStatus) OR (#SyncStatus = -1) OR (ISNULL(#SyncStatus,-1) = -1))
END
When calling this SP as this:
db.SqlList<ScanRecord>("EXEC sproc_WS_SelectScanFeedScanRecords #SyncStatus",new {SyncStatus = 1});
It returns all records with JobNo = 1 instead of SyncStatus=1 because it ignores the named parameter and add by the order in which they are defined in the SP.
I have to call it like this:
db.SqlList<ScanRecord>("EXEC sproc_WS_SelectScanFeedScanRecords #SyncStatus=1");
Is this expected behavior? I think it defeats the anonymous type parameters if I can't trust it
TIA
Bo
My solution was to roll my own methods for stored procedures. If people finds them handy, I could add them to the project
public static void StoredProcedure(this IDbConnection dbConn, string storedprocedure, object anonType = null)
{
dbConn.Exec(dbCmd =>
{
dbCmd.CommandType = CommandType.StoredProcedure;
dbCmd.CommandText = storedprocedure;
dbCmd.SetParameters(anonType, true);
dbCmd.ExecuteNonQuery();
});
}
public static T StoredProcedureScalar<T>(this IDbConnection dbConn, string storedprocedure, object anonType = null)
{
return dbConn.Exec(dbCmd =>
{
dbCmd.CommandType = CommandType.StoredProcedure;
dbCmd.CommandText = storedprocedure;
dbCmd.SetParameters(anonType, true);
using (IDataReader reader = dbCmd.ExecuteReader())
return GetScalar<T>(reader);
});
}
public static List<T> StoredProcedureList<T>(this IDbConnection dbConn, string storedprocedure, object anonType = null)
{
return dbConn.Exec(dbCmd =>
{
dbCmd.CommandType = CommandType.StoredProcedure;
dbCmd.CommandText = storedprocedure;
dbCmd.SetParameters(anonType, true);
using (var dbReader = dbCmd.ExecuteReader())
return IsScalar<T>()
? dbReader.GetFirstColumn<T>()
: dbReader.ConvertToList<T>();
});
}
They are just modified versions of the SqlScalar and SqlList plus the ExecuteNonQuery

What is the PostFileWithRequest equivalent in ServiceStack's 'New API'?

I want to post some request values alongside the multipart-formdata file contents. In the old API you could use PostFileWithRequest:
[Test]
public void Can_POST_upload_file_using_ServiceClient_with_request()
{
IServiceClient client = new JsonServiceClient(ListeningOn);
var uploadFile = new FileInfo("~/TestExistingDir/upload.html".MapProjectPath());
var request = new FileUpload{CustomerId = 123, CustomerName = "Foo"};
var response = client.PostFileWithRequest<FileUploadResponse>(ListeningOn + "/fileuploads", uploadFile, request);
var expectedContents = new StreamReader(uploadFile.OpenRead()).ReadToEnd();
Assert.That(response.FileName, Is.EqualTo(uploadFile.Name));
Assert.That(response.ContentLength, Is.EqualTo(uploadFile.Length));
Assert.That(response.Contents, Is.EqualTo(expectedContents));
Assert.That(response.CustomerName, Is.EqualTo("Foo"));
Assert.That(response.CustomerId, Is.EqualTo(123));
}
I can't find any such method in the new API, nor any overrides on client.Post() which suggest that this is still possible. Does anyone know if this is a feature that was dropped?
Update
As #Mythz points out, the feature wasn't dropped. I had made the mistake of not casting the client:
private IRestClient CreateRestClient()
{
return new JsonServiceClient(WebServiceHostUrl);
}
[Test]
public void Can_WebRequest_POST_upload_binary_file_to_save_new_file()
{
var restClient = (JsonServiceClient)CreateRestClient(); // this cast was missing
var fileToUpload = new FileInfo(#"D:/test/test.avi");
var beforeHash = this.Hash(fileToUpload);
var response = restClient.PostFileWithRequest<FilesResponse>("files/UploadedFiles/", fileToUpload, new TestRequest() { Echo = "Test"});
var uploadedFile = new FileInfo(FilesRootDir + "UploadedFiles/test.avi");
var afterHash = this.Hash(uploadedFile);
Assert.That(beforeHas, Is.EqualTo(afterHash));
}
private string Hash(FileInfo file)
{
using (var md5 = MD5.Create())
{
using (var stream = file.OpenRead())
{
var bytes = md5.ComputeHash(stream);
return BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLower();
}
}
}
None of the old API was removed from the C# Service Clients, only new API's were added.
The way you process an uploaded file inside a service also hasn't changed.

How do I create an OrderBy statement using a reflected value?

I would like to create a method that orders an IEnumerable List by a given property where the property is passed into the method by a string i.e. (Mind you the first code example does not work, but the second does and is what I am trying to emulate dynamically).
string sortName = "SerialNumber";
IEnumerable<PartSummary> partList = FunctionToCreateList();
partOrderedList = partList.OrderBy(what do I stick in here);
that would be equivalent to
IEnumerable<PartSummary> partList = FunctionToCreateList();
partOrderedList = partList.OrderBy(p => p.SerialNumber);
How can I accomplish this?
Are you saying you want to pass the order by in to your method? If so, you can use this:
Expression<Func<PartSummary, bool>> orderByClause
Then you can do this:
partOrderedList = partList.OrderBy(orderByClause);
Then you can handle your order by in your business layer or wherever you wish.
Okay, update: If you want to pass in the column name as a string you can do something like as follows:
Create a static class for an extension method (reference: http://social.msdn.microsoft.com/Forums/en-US/linqprojectgeneral/thread/39028ad2-452e-409f-bc9e-d1b263e921f6/):
static class LinqExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortingColumn, bool isAscending)
{
if (String.IsNullOrEmpty(sortingColumn))
{
return source;
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
MemberExpression property = Expression.Property(parameter, sortingColumn);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = isAscending ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
}
Then you can create your method:
static IQueryable<PartSummary> FunctionToCreateList()
{
IList<PartSummary> list = new List<PartSummary>();
list.Add(new PartSummary
{
Id = 1,
SerialNumber = "A",
});
list.Add(new PartSummary
{
Id = 2,
SerialNumber = "B",
});
return list.AsQueryable();
}
And then call your method:
static void Main(string[] args)
{
IQueryable<PartSummary> partOrderedList = FunctionToCreateList();
PartSummary partSummary = new PartSummary();
string sortBy = "Id";
partOrderedList = partOrderedList.OrderBy(sortBy, false);
foreach (PartSummary summary in partOrderedList)
{
Console.WriteLine(summary.Id + ", " + summary.SerialNumber);
}
Console.ReadLine();
}
Now you can pass in the column name as a string and sort.
Hope this helps!
You can also avoid extending and just use a compiled expression tree to accomplish this:
public Func<T, object> ResolveToProperty<T>(String propertyName)
{
Type t = typeof(T);
var paramExpression = Expression.Parameter(t, "element");
var propertyExpression = Expression.Property(paramExpression, propertyName);
return Expression.Lambda<Func<T, object>>(propertyExpression, paramExpression).Compile();
}
string sortName = "SerialNumber";
IEnumerable<PartSummary> partList = FunctionToCreateList();
var partOrderedList = partList.OrderBy(ResolveToProperty<PartSummary>(sortName));

Resources