In my MVC app I'm using LINQ to retrieve data from DB and PagedList for pagination. I have a couple of questions, after the code block, that I would like some help with.
Function where I retrieve data from cache or DB:
public NewsPagedListDTO GetNewsFromCacheOrDB(int pageSize, int? newsID, int? page, string newsTitle, int? categoryID, int? orderByTitle, int? orderByPublisher, int? orderByDate, int? orderByCategory)
{
DataCache cache = new DataCache("default");
object cacheNews = cache.Get("cacheNews");
List<News> news = new List<News>();
if (cacheNews == null)
{
news = (from n in DB.News
select n).ToList();
//Only cache if no parameters was provided
if (newsID == null && newsTitle == null && categoryID == null && orderByTitle == null && orderByPublisher == null &&
orderByDate == null && orderByCategory == null)
cache.Add("cacheNews", news);
}
}
else
{
news = (List<News>)cacheNews;
}
if (newsID != null)
news = news.Where(n => n.NewsID == newsID).ToList();
if (categoryID != null)
news = news.Where(n => n.CategoryID == categoryID).ToList();
if (newsTitle != null)
news = news.Where(n => n.Title == newsTitle).ToList();
if (orderByTitle != null)
if (orderByTitle == 0)
news = news.OrderBy(n => n.Title).ToList();
else
news = news.OrderByDescending(n => n.Title).ToList();
if (orderByPublisher != null)
if (orderByPublisher == 0)
news = news.OrderBy(n => n.PublishedByFullName).ToList();
else
news = news.OrderByDescending(n => n.PublishedByFullName).ToList();
if (orderByDate != null)
if (orderByDate == 0)
news = news.OrderByDescending(n => n.DatePublished).ToList();
else
news = news.OrderBy(n => n.DatePublished).ToList();
if (orderByCategory != null)
if (orderByCategory == 0)
news = news.OrderBy(n => n.CategoryToString).ToList();
else
news = news.OrderByDescending(n => n.CategoryToString).ToList();
List<NewsDTO> newsDTO = new List<NewsDTO>();
foreach (var item in news)
{
NewsDTO newsDTOtemp = new NewsDTO();
newsDTOtemp.BlobName = item.BlobName;
newsDTOtemp.DatePublished = item.DatePublished;
newsDTOtemp.NewsID = item.NewsID;
newsDTOtemp.PreviewText = item.PreviewText;
newsDTOtemp.PublishedByEmail = item.PublishedByEmail;
newsDTOtemp.PublishedByFullName = item.PublishedByFullName;
newsDTOtemp.PublishedByID = item.PublishedByID;
newsDTOtemp.Title = item.Title;
newsDTOtemp.CategoryID = item.Category.CategoryID;
newsDTOtemp.CategoryToString = item.Category.Name;
newsDTO.Add(newsDTOtemp);
}
//Pagination
NewsPagedListDTO newsResultDTO = new NewsPagedListDTO();
newsResultDTO.NewsDTO = (PagedList<NewsDTO>)newsDTO.ToPagedList(page ?? 1, pageSize);
return newsResultDTO;
}
Pagination in my view:
#Html.PagedListPager(Model.NewsPagedListDTO.NewsDTO, page => Url.Action("News", new
{
page,
newsTitle = Request.QueryString["NewsTitle"],
categoryID = Request.QueryString["categoryID"],
orderByTitle = Request.QueryString["orderByTitle"],
orderByPublisher = Request.QueryString["orderByPublisher"],
orderByDate = Request.QueryString["orderByDate"],
orderByCategory = Request.QueryString["orderByCategory"]
}),
new PagedListRenderOptions()
{
Display = PagedListDisplayMode.IfNeeded,
MaximumPageNumbersToDisplay = 5,
DisplayEllipsesWhenNotShowingAllPageNumbers = false,
DisplayLinkToPreviousPage = PagedListDisplayMode.Never,
DisplayLinkToNextPage = PagedListDisplayMode.Never,
LinkToFirstPageFormat = String.Format("«"),
LinkToLastPageFormat = String.Format("»")
})
Questions
It's the first time I'm using PagedList. What's the point in having postback for changing the page when the full results is retrieved? Isn't it better with client side pagination then? Currently I am retrieving all posts from DB with:
news = (from n in DB.News
select n).ToList();
And after data is retrieved, sort with parameters..
Sure the result is easy to cache but.. I rather only get data for just one page.
How would I only get data for the current page with my optional parameters? I have used stored procedures for this before but I don't think it's possible with PagedList.
How can I have cleaner code for optional parameters in my LINQ query? I don't like all those if statements..
The thing is you have to Skip items and then Take(pageSize)
var pagedNews = DB.News.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
So let's say you have 5 items / page.
If you are on page 1
(1 - 1) * 5 = 0 so skip zero Items and take 5
If you are on page 2
(2 - 1) * 5 = 5 so skip 5 Items and take 5
Your parameters are Nullable so you might have to put a default condition on your parameters say if NULL then PageSize = 5 and PageNumber = 1
int pageSize, int? newsID, int? page
EDIT:
Instead of:
if (cacheNews == null)
{
news = (from n in DB.News
select n).ToList();
...........
}
Use this:
// You will have to OrderBy() before doing the pagination:
// Read as Queryable()
var pagedNews = DB.News.AsQueryable();
// Apply OrderBy Logic
pagedNews = pagedNews.OrderBy();
//ApplyPagination
pagedNews = pagedNews.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
ORDER BY
You don't need to pass the OrderBy columns as separate strings.
Pass one string e.g. selectedSortBy from View,
I have created a Helper method:
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Common.Helpers
{
public static class PaginationHelper
{
public static IQueryable<T> ApplyPagination<T>(IQueryable<T> source, Pagination pagination)
{
var sortDirection = pagination.SortDirection == SortDirectionEnum.Ascending ? "OrderBy" : "OrderByDescending";
var orderBy = pagination.SortBy ?? pagination.DefaultSortBy;
return source.OrderBy(orderBy, sortDirection).Skip((pagination.PageNumber - 1) * pagination.PageSize).Take(pagination.PageSize);
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, string sortDirection, params object[] values)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var resultExp = Expression.Call(typeof(Queryable), sortDirection, new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
}
}
Pagination Model + Enum:
namespace Common.Helpers
{
public class Pagination
{
public SortDirectionEnum SortDirection { get; set; }
public string SortBy { get; set; }
public int TotalRecords { get; set; }
public int NumberOfPages { get; set; }
public int PageSize { get; set; }
public int PageNumber { get; set; }
public string DefaultSortBy { get; set; }
public string ReloadUrl { get; set; }
public string TargetDiv { get; set; }
public Pagination()
{
}
public Pagination(string reloadUrl, string targetDiv, int totalRecords, int numberOfPages)
{
ReloadUrl = reloadUrl;
TargetDiv = targetDiv;
PageSize = 10;
PageNumber = 1;
}
}
public enum SortDirectionEnum
{
Ascending = 1,
Descending = 2
}
}
Then call your Query like this:
var items = DB.News.AsQueryable();
items = PaginationHelper.ApplyPagination(items, PAGINATION_MODEL);
Related
I have tried to upload a csv file to my application but this not showing all rows, I am geting only one row here is my code
Model
public class FileUploadViewModel
{
public string Name { get; set; }
public string School { get; set; }
public string Age { get; set; }
public string DOB { get; set; }
public string ParentName { get; set; }
}
Controller
[HttpPost]
public ActionResult AddparcelView(FileUploadViewModel filedata)
{
var attachedFile = System.Web.HttpContext.Current.Request.Files["CsvDoc"];
if (attachedFile == null || attachedFile.ContentLength <= 0) return Json(null);
var csvReader = new StreamReader(attachedFile.InputStream);
var uploadModelList = new List<FileUploadViewModel>();
string inputDataRead;
var values = new List<string>();
var uploadModelRecord = new FileUploadViewModel();
var jsonToOutput = "";
while ((inputDataRead = csvReader.ReadLine()) != null)
{
values.Add(inputDataRead.Trim().Replace(" ", "").Replace(",", " "));
}
values.Remove(values[0]);
values.Remove(values[values.Count - 1]);
var i = 0;
foreach (var value in values)
{
i++;
var eachValue = value.Split(' ');
uploadModelRecord.Name = eachValue[0] != "" ? eachValue[0] : string.Empty;
uploadModelRecord.School = eachValue[1] != "" ? eachValue[1] : string.Empty;
uploadModelRecord.Age = eachValue[2] != "" ? eachValue[2] : string.Empty;
uploadModelRecord.DOB = eachValue[3] != "" ? eachValue[3] : string.Empty;
uploadModelRecord.ParentName = eachValue[4] != "" ? eachValue[4] : string.Empty;
//jsonToOutput = JsonConvert.SerializeObject(uploadModelRecord, Formatting.Indented);
Console.WriteLine(i);
}
return Json(uploadModelRecord);
}
I have five rows at this CSV file but i getting only one row like this
Age: "sdfdsf"
DOB: "test"
Name:"mohammad2"
ParentName:"test"
School:"32"
So this is just one row data I am showing up with json, but i not getting the full json array with other 4rows please help me, Thanks
You are using the same instance uploadModelRecord in every iteration of your loop. In the first iteration it gets the values of the first line. In the second iteration those values get overwritten with the values of the second line. And so on.
Create a new instance in each iteration and add them to a list. Return the serialized list.
public ActionResult AddparcelView(FileUploadViewModel filedata)
{
...
//Remove line for variable uploadModelRecord.
var uploadModelRecords = new List<FileUploadViewModel>(); //List instead of single element.
foreach (var value in values)
{
i++;
var eachValue = value.Split(' ');
var uploadModelRecord = new FileUploadViewModel(); //Variable for current line.
uploadModelRecord.Name = eachValue[0] != "" ? eachValue[0] : string.Empty;
uploadModelRecord.School = eachValue[1] != "" ? eachValue[1] : string.Empty;
uploadModelRecord.Age = eachValue[2] != "" ? eachValue[2] : string.Empty;
uploadModelRecord.DOB = eachValue[3] != "" ? eachValue[3] : string.Empty;
uploadModelRecord.ParentName = eachValue[4] != "" ? eachValue[4] : string.Empty;
//jsonToOutput = JsonConvert.SerializeObject(uploadModelRecord, Formatting.Indented);
Console.WriteLine(i);
uploadModelRecords.Add(uploadModelRecord); //Here you add the variable for current line to the list.
}
return Json(uploadModelRecords); //Return the list instead of a single instance.
}
I am having some trouble figuring out the right way to go about creating a dynamic query that I can use values from DropDownList controls to filter and sort/order the results of a database query to fill a ListView. I am able to hard code individual queries, which works ok, except for the fact that it takes an incredible amount of effort, and is not easily changed.
My code is as follows (using all filters):
queryResult = From product In myEntities.InventoryProducts
Where product.VendorID = ddlFilterVendor.SelectedValue And product.ItemType = ddlItemType.SelectedValue And product.LabelSize = ddlLabelSize.SelectedValue And product.PrintLabel = boolPrint And product.Edited = boolEdited
Order By product.ID Ascending
Select product
Return queryResult
Is there a better method to this? I would like to be able to select the value from each DropDownList and generate a custom WHERE clause, as well as an ORDER BY clause.
Any help would be greatly appreciated, thanks.
I can give you a simple example as to how to to proceed with your idea. I am sure if you look through StackOverflow or search via google you will get code that does a better job of dynamic expression building. The same concept can be used for order by.
void Main()
{
var ops = new List<Ops>
{
new Ops
{
OperandType = typeof(string),
OpType=OpType.Equals,
OperandName = "Name",
ValueToCompare = "MM" // in your case this will be the values from the dropdowns
},
new Ops
{
OperandType = typeof(int),
OpType=OpType.Equals,
OperandName = "ID",
ValueToCompare = 1
},
};
var testClasses = new List<TestClass>
{
new TestClass { ID =1, Name = "MM", Date = new DateTime(2014,12,1)},
new TestClass { ID =2, Name = "BB", Date = new DateTime(2014,12,2)}
};
// this will produce prop => ((prop.Name == "MM") And (prop.ID == 1))
var whereDelegate = ExpressionBuilder.BuildExpressions<TestClass>(ops);
foreach(var item in testClasses.Where(whereDelegate))
{
Console.WriteLine("ID " +item.ID);
Console.WriteLine("Name " +item.Name);
Console.WriteLine("Date" + item.Date);
}
}
// Define other methods and classes here
public enum OpType
{
Equals
}
public class Ops
{
public Type OperandType {get; set;}
public OpType OpType {get; set;}
public string OperandName {get;set;}
public object ValueToCompare {get;set;}
}
public class TestClass
{
public int ID {get;set;}
public string Name {get; set;}
public DateTime Date {get;set;}
}
public class ExpressionBuilder
{
public static Func<T,bool> BuildExpressions<T>( List<Ops> opList)
{
Expression currentExpression= null;
var parameterExpression = Expression.Parameter(typeof(T), "prop");
for(int i =0; i< opList.Count; i++)
{
var op = opList[i];
Expression innerExpression = null;
switch(op.OpType)
{
case OpType.Equals :
{
var propertyExpression = Expression.Property(parameterExpression ,
op.OperandName);
var constExpression = Expression.Constant(op.ValueToCompare);
innerExpression = Expression.Equal(propertyExpression,
constExpression);
break;
}
}
if (i >0)
{
currentExpression = Expression.And(currentExpression, innerExpression);
}
else
{
currentExpression = innerExpression;
}
}
var lambdaExpression = Expression.Lambda<Func<T,bool>>(currentExpression,
new []{parameterExpression });
Console.WriteLine(lambdaExpression);
return lambdaExpression.Compile() ;
}
}
My stored procedure is returning dynamic pivot columns as available. I am using the SQLMapper and COlumnTypeAttribute, But in the results I can only see the first column and its value, but the dynamic pivot column(s) and their values(s) are empty.
the sample data could look like
The first column is fixed, Rest of the columns are pivot columns.
TSBNumber SystemXY SystemBB SystemTT
asdas 1/1/2013
1231 1/1/2014
12312 1/1/2013
ASAWS 1/1/2013
awsdS 1/1/2013
Store Procedure
DECLARE #PivotColumnHeaders NVARCHAR(MAX)
SELECT #PivotColumnHeaders =
COALESCE(
#PivotColumnHeaders + ',[' + cast(SystemFullName as Nvarchar) + ']',
'[' + cast(SystemFullName as varchar)+ ']'
)
FROM System
WHERE (#SelectedSystemIDs IS NULL OR System.ID IN(select * from dbo.SplitInts_RBAR_1(#SelectedSystemIDs, ',')))
AND ((#PlatformID IS NULL) OR (System.PlatformID = #PlatformID) OR (#PlatformID = 12 AND System.PlatformID <= 2))
DECLARE #PivotTableSQL NVARCHAR(MAX)
SET #PivotTableSQL = N'
SELECT *
FROM (
SELECT
TSBNumber [TSBNumber],
SystemFullName,
ClosedDate
FROM ServiceEntry
INNER JOIN System
ON ServiceEntry.SystemID = System.ID
where
(ServiceEntry.TSBNumber IS NOT NULL)
AND
(ServiceEntry.ClosedDate IS NOT NULL)
AND
(
(''' + #SelectedTsbIDs + ''' = '''+ '0' + ''') OR
(ServiceEntry.TSBNumber in (select * from dbo.SplitStrings_Moden(''' + #SelectedTsbIDs + ''', ''' + ',' + ''')))
)
) AS PivotData
PIVOT (
MAX(ClosedDate)
FOR SystemFullName IN (
' + #PivotColumnHeaders + '
)
) AS PivotTable
'
EXECUTE (#PivotTableSQL)
ColumnAttributeTypeManager
namespace RunLog.Domain.Exports
{
/// <summary>
/// Uses the Name value of the ColumnAttribute specified, otherwise maps as usual.
/// </summary>
/// <typeparam name="T">The type of the object that this mapper applies to.</typeparam>
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
public static readonly string ColumnAttributeName = "ColumnAttribute";
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
//public ColumnAttributeTypeMapper()
// : base(new SqlMapper.ITypeMap[]
// {
// new CustomPropertyTypeMap(typeof (T), SelectProperty),
// new DefaultTypeMap(typeof (T))
// })
//{
//}
//private static PropertyInfo SelectProperty(Type type, string columnName)
//{
// return
// type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).
// FirstOrDefault(
// prop =>
// prop.GetCustomAttributes(false)
// // Search properties to find the one ColumnAttribute applied with Name property set as columnName to be Mapped
// .Any(attr => attr.GetType().Name == ColumnAttributeName
// &&
// attr.GetType().GetProperties(BindingFlags.Public |
// BindingFlags.NonPublic |
// BindingFlags.Instance)
// .Any(
// f =>
// f.Name == "Name" &&
// f.GetValue(attr).ToString().ToLower() == columnName.ToLower()))
// && // Also ensure the property is not read-only
// (prop.DeclaringType == type
// ? prop.GetSetMethod(true)
// : prop.DeclaringType.GetProperty(prop.Name,
// BindingFlags.Public | BindingFlags.NonPublic |
// BindingFlags.Instance).GetSetMethod(true)) != null
// );
//}
}
public class MyModel
{
[Column("TSBNumber")]
public string TSBNumber { get; set; }
[Column(Name = "FileKey")]
public string MyProperty2 { get; set; }
//public string MyProperty2 { get; set; } // Uses Default Mapping
// ...
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute() { }
public ColumnAttribute(string Name) { this.Name = Name; }
}
public class FallbackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
foreach (var mapper in _mappers)
{
try
{
ConstructorInfo result = mapper.FindConstructor(names, types);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetConstructorParameter(constructor, columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetMember(columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
}
}
Executing Stored Procedure
public static string ServiceTsbExport(DateTime StartDate, DateTime EndDate, int UserRoleID,
string SelectedSystemIDs,
string SelectedTsbIDs)
{
EFDbContext db = new EFDbContext();
Dapper.SqlMapper.SetTypeMap(typeof(MyModel), new ColumnAttributeTypeMapper<MyModel>());
return db.Database.SqlQuery<MyModel>("[dbo].[spExportServiceTSB] #parm1, #parm2, #parm3, #parm4, #parm5",
new SqlParameter("parm1", StartDate),
new SqlParameter("parm2", EndDate),
new SqlParameter("parm3", SelectedSystemIDs),
new SqlParameter("parm4", SelectedTsbIDs),
new SqlParameter("parm5", UserRoleID)
).ToList().ToHTMLTable();
}
I think you're making this very hard while it could be simple. I've once done almost exactly the same thing. Here's an anonymized version of it:
using Dapper;
...
using (var cnn = new SqlConnection(#"Data Source=... etc."))
{
cnn.Open();
var p = new DynamicParameters();
p.Add("#params", "Id=21");
var obs = cnn.Query(sql:"GetPivotData", param: p,
commandType:CommandType.StoredProcedure);
var dt = ToDataTable(obs);
}
This ToDataTable method is probably similar to your ToHTMLTable method. Here it is:
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
if (items == null) return null;
var data = items.ToArray();
if (data.Length == 0) return null;
var dt = new DataTable();
foreach(var pair in ((IDictionary<string, object>)data[0]))
{
dt.Columns.Add(pair.Key, (pair.Value ?? string.Empty).GetType());
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}
The heart of the logic is that the dynamic returned by Dapper's Query() extension method can be cast to an IDictionary<string, object>.
I am using DevExpress control within an ASP.NET MVC 4 project.
I am using unbound columns in the GridView extension for ASP.NET MVC. In the CustomUnboundColumnData event handler, the e.GetListSourceFieldValue always returns null for me.
I then tried getting the value directly from the model instead of calling this method (please see the commented line just above the call to the method) but even though that worked, it had side-effects, which I won't get into just now.
I am using ASP.NET MVC 4 with Visual Web Developer Express 2010 edition. I am using DevExpress extensions for MVC v12.2.10.0.
My OS is Windows 7, 64-bit. However, the extensions I am using are 32-bit only.
I cannot ship my whole solution as it is broken down into multiple projects, most of which have lots of IP code I've written for my client. But here are the relevant pieces from my code.
Index.cshtml (Razor View Engine)
-------------------------------------------------------------------
#model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
#{
ViewBag.Title = "Strings";
}
<div id = "pageCaption">Strings</div>
#Html.Partial("_StringsPartial", Model)
_StringsPartial.cshtml (Razor View Engine)
-------------------------------------------------------------------
#using System.Web.UI.WebControls;
#using System.Data;
#model List<GlobalizationUI.Presentation.ViewModels.StringTableRow>
#Html.DevExpress().GridView(settings =>
{
settings.Name = "gvStrings";
settings.CallbackRouteValues = new { Controller = "Strings", Action = "StringsPartial" };
settings.Width = 1200;
settings.SettingsPager.Position = PagerPosition.TopAndBottom;
settings.SettingsPager.FirstPageButton.Visible = true;
settings.SettingsPager.LastPageButton.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Visible = true;
settings.SettingsPager.PageSizeItemSettings.Items = new string[] { "10", "20", "50", "100", "200" };
settings.SettingsPager.PageSize = 50;
settings.Settings.ShowFilterRow = true;
settings.Settings.ShowFilterRowMenu = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.ClearFilterButton.Visible = true;
settings.Settings.ShowHeaderFilterButton = true;
settings.KeyFieldName = "ResourceKeyId";
settings.Columns.Add("Key");
var categoryColumn = settings.Columns.Add("CategoryId", "Category");
categoryColumn.ColumnType = MVCxGridViewColumnType.ComboBox;
var categoryColumnEditProperties = categoryColumn.PropertiesEdit as ComboBoxProperties;
categoryColumnEditProperties.DataSource = ViewBag.AllCategories;
categoryColumnEditProperties.TextField = "Name";
categoryColumnEditProperties.ValueField = "Id";
categoryColumnEditProperties.ValueType = typeof(long);
if (Model != null && Model.Count > 0 &&
Model[0] != null && Model[0].StringValues != null && Model[0].StringValues.Count > 0)
{
foreach (var kvp in Model[0].StringValues)
{
settings.Columns.Add(col =>
{
col.FieldName = kvp.CultureShortName;
col.Caption = kvp.CultureShortName;
col.UnboundType = DevExpress.Data.UnboundColumnType.Object;
col.SetDataItemTemplateContent(container => { ViewContext.Writer.Write(DataBinder.Eval(container.DataItem, col.FieldName + ".StringValue")); });
col.SetEditItemTemplateContent(container =>
{
Html.DevExpress().TextBox(s =>
{
s.Name = string.Format("txt{0}", kvp.CultureShortName);
}).Bind(kvp.StringValue).Render();
});
});
}
}
settings.CustomUnboundColumnData = (sender, e) =>
{
var fixedColumns = new List<string> { "ResourceKeyId", "Key", "CategoryId" };
if (!fixedColumns.Contains(e.Column.FieldName))
{
if (e.IsGetData)
{
try
{
// var values = Model[e.ListSourceRowIndex].StringValues;
var values = e.GetListSourceFieldValue(e.ListSourceRowIndex, "StringValues") as IList<GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue>;
if (values != null)
{
var value = values.FirstOrDefault(pair => pair.CultureShortName == e.Column.FieldName);
var defaultValue = default(GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue);
e.Value = value.Equals(defaultValue) ? defaultValue : new GlobalizationUI.Presentation.ViewModels.CultureNameAndStringValue();
}
}
catch (Exception ex)
{
System.Diagnostics.Debugger.Break();
System.Diagnostics.Debug.Print(ex.ToString());
}
}
}
};
foreach (GridViewDataColumn column in settings.Columns)
{
column.Settings.HeaderFilterMode = HeaderFilterMode.CheckedList;
}
settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Strings", Action = "CreateNew" };
settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Strings", Action = "Edit" };
settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Strings", Action = "Delete" };
settings.SettingsEditing.Mode = GridViewEditingMode.Inline;
settings.SettingsBehavior.ConfirmDelete = true;
settings.CommandColumn.Visible = true;
settings.CommandColumn.NewButton.Visible = true;
settings.CommandColumn.EditButton.Visible = true;
settings.CommandColumn.UpdateButton.Visible = true;
settings.CommandColumn.DeleteButton.Visible = true;
}).Bind(Model).GetHtml()
StringsController
-------------------------------------------------------------------
using System.Data;
using System.Web.Mvc;
using GlobalizationUI.BusinessObjects;
using Resources.BaseServices.Caching;
using System.Collections.Generic;
using System.ComponentModel;
using GlobalizationUI.Presentation.ViewModels;
using Resources.Util;
namespace GlobalizationUI.Presentation.Controllers
{
public class StringsController : Controller
{
private static string CacheKey_StringTable = "CacheKey_StringTable";
private static string CacheKey_AllCategories = "CacheKey_AllCategories";
private static object padLock = new object();
public ActionResult Index()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return View(stringTable);
}
public ActionResult StringsPartial()
{
var stringTable = GetStringTable();
ViewBag.AllCategories = GetCategoryList();
return PartialView("_StringsPartial", stringTable);
}
[HttpPost]
public ActionResult CreateNew(StringTableRow row)
{
System.Diagnostics.Debugger.Break();
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(row))
{
System.Diagnostics.Debug.Print(prop.Name);
}
return Content("Hello, there!");
}
[HttpPost]
public ActionResult Edit(DataRow row)
{
return new EmptyResult();
}
[HttpPost]
public ActionResult Delete(long resourceKeyId)
{
return new EmptyResult();
}
private IEnumerable<Category> GetCategoryList()
{
lock (padLock)
{
if (CacheManager.Contains(CacheKey_AllCategories))
{
return CacheManager.Get<IEnumerable<Category>>(CacheKey_AllCategories);
}
}
var list = Category.All;
lock (padLock)
{
CacheManager.Add(CacheKey_AllCategories, list);
}
return list;
}
private List<StringTableRow> GetStringTable()
{
List<StringTableRow> stringTable;
lock (padLock)
{
if (CacheManager.Contains(CacheKey_StringTable))
{
return CacheManager.Get<List<StringTableRow>>(CacheKey_StringTable);
}
}
stringTable = new StringTable().ToListOfStringTableRows();
lock (padLock)
{
CacheManager.Add(CacheKey_StringTable, stringTable);
}
return stringTable;
}
}
}
View Models
-------------------------------------------------------------------
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public List<CultureNameAndStringValue> StringValues { get; set; }
}
}
namespace GlobalizationUI.Presentation.ViewModels
{
public class CultureNameAndStringValue
{
public CultureNameAndStringValue() : this(null, null) { }
public CultureNameAndStringValue(string cultureShortName, string stringValue)
{
CultureShortName = cultureShortName;
StringValue = stringValue;
}
public string CultureShortName { get; set; }
public string StringValue { get; set; }
}
}
Model:
-------------------------------------------------------------------
using System.Data;
using GlobalizationUI.Data;
namespace GlobalizationUI.BusinessObjects
{
public class StringTable : DataTable
{
public StringTable()
{
var sql = #"
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when s.CultureId = ' + cast(c.Id as nvarchar(max)) +
' then s.ResourceValue end) as ' + quotename(c.ShortName)
from Culture as c
where c.Supported = 1
select #stmt = '
select
rk.Id AS ResourceKeyId,
rk.Name AS [Key],
c.Id AS CategoryId,
c.Name as CategoryName, ' + #stmt + '
from StringCategory as sc
LEFT OUTER join Category as c on c.Id = sc.CategoryId
RIGHT OUTER JOIN ResourceKey as rk on rk.Id = sc.ResourceKeyId
inner join Strings as s on s.ResourceKeyId = rk.Id
group by rk.Id, rk.Name, c.Id, c.Name
'
exec sp_executesql #stmt = #stmt;";
this.Merge(Database.DefaultDatabase.GetDataTable(sql));
}
}
}
Model to View Model conversion:
-------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlTypes;
namespace GlobalizationUI.Presentation.ViewModels
{
public static class DataTableExtensions
{
public static List<StringTableRow> ToListOfStringTableRows(this DataTable dataTable)
{
var ret = new List<StringTableRow>();
if (dataTable == null || dataTable.Rows.Count == 0) return ret;
foreach (DataRow row in dataTable.Rows)
{
StringTableRow stringTableRow = new StringTableRow();
foreach (DataColumn column in dataTable.Columns)
{
if (string.Compare(column.ColumnName, "ResourceKeyId", true) == 0)
{
stringTableRow.ResourceKeyId = (long)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "Key", true) == 0)
{
stringTableRow.Key = (string)row[column.ColumnName];
}
else if (string.Compare(column.ColumnName, "CategoryId", true) == 0)
{
var categoryId = row[column.ColumnName];
stringTableRow.CategoryId = categoryId == DBNull.Value ? 0 : (long)categoryId;
}
else if (string.Compare(column.ColumnName, "CategoryName", true) == 0)
{
continue;
}
else
{
if (stringTableRow.StringValues == null)
stringTableRow.StringValues = new List<CultureNameAndStringValue>();
stringTableRow.StringValues.Add(new CultureNameAndStringValue(column.ColumnName, (string)row[column.ColumnName]));
}
}
ret.Add(stringTableRow);
}
return ret;
}
}
}
The user interface is supposed to look somewhat like the the picture shown above. The number of columns returned by the SQL query is variable depending upon the number of cultures supported by a particular installation/deployment/business client. Hence the need for using unbound columns.
I took this picture not through this code but through an older version of my code when I bound data directly to a System.Data.DataTable and did not use any unbound columns. That was all well until I had to edit the data in the actions at the MVC/server side. So, I switched from using a DataTable to a POCO and used unbound columns for all cultures.
Kindly help.
Okay, though I posted this question only 2 hours or so ago, I've had this trouble for over 8 hours now and have been trying various ways to solve this problem.
And just now, one of the things I did made this problem go away. Here it is.
If you are binding your DevExpress GridViewExtension for ASP.NET MVC to a custom POCO like mine, and that POCO has one or more members that are collections of any kind, then, you must and absolutely must initialize those properties that represent collections in the constructor of your POCO.
For e.g., in my case, the model was a list of StringTableRow's, where StringTableRow was a POCO.
In the StringTableRow class, I had a property named StringValues which was of type IList<CultureNameAndStringValue>, which is a collection. Therefore, for my code to work, I initialized the StringValues property in the construct of my StringTableRow class and everything started to work.
using System.Collections.Generic;
namespace GlobalizationUI.Presentation.ViewModels
{
public class StringTableRow
{
public StringTableRow()
{
// I added this ctor and initialized the
// StringValues property and the broken
// custom binding to unbound columns started
// to work.
StringValues = new List<CultureNameAndStringValue>();
}
public long ResourceKeyId { get; set; }
public string Key { get; set; }
public long CategoryId { get; set; }
public IList<CultureNameAndStringValue> StringValues { get; set; }
}
}
I am trying to sort an IQueryable object by a specific column via a string input.
Calling .ToList() on the IQueryable and sorting via a list column works perfectly, however when sorting a date column, it sorts alphabetically, which is not ideal.
If anybody could point me in the correct direction here, I'd appreciate it.
My Usage
IQueryable<MyItemType> list = (from t1 in db.MyTable
select t1);
List<MyItemType> itemsSorted; // Sort here
if (!String.IsNullOrEmpty(OrderBy))
{
itemsSorted = list.OrderBy(OrderBy).ToList();
}
else
{
itemsSorted = list.ToList();
}
Extension Method
using System.Linq;
using System.Collections.Generic;
using System;
using System.Linq.Expressions;
using System.Reflection;
public static class OrderByHelper
{
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, string orderBy)
{
return enumerable.AsQueryable().OrderBy(orderBy).AsEnumerable();
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, string orderBy)
{
foreach (OrderByInfo orderByInfo in ParseOrderBy(orderBy))
collection = ApplyOrderBy<T>(collection, orderByInfo);
return collection;
}
private static IQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo)
{
string[] props = orderByInfo.PropertyName.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
string methodName = String.Empty;
if (!orderByInfo.Initial && collection is IOrderedQueryable<T>)
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "ThenBy";
else
methodName = "ThenByDescending";
}
else
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "OrderBy";
else
methodName = "OrderByDescending";
}
//TODO: apply caching to the generic methodsinfos?
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { collection, lambda });
}
private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy)
{
if (String.IsNullOrEmpty(orderBy))
yield break;
string[] items = orderBy.Split(',');
bool initial = true;
foreach (string item in items)
{
string[] pair = item.Trim().Split(' ');
if (pair.Length > 2)
throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
string prop = pair[0].Trim();
if (String.IsNullOrEmpty(prop))
throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
SortDirection dir = SortDirection.Ascending;
if (pair.Length == 2)
dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
initial = false;
}
}
private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}
private enum SortDirection
{
Ascending = 0,
Descending = 1
}
public static IQueryable<T> OrderByIQueryableStringValue<T>(this IQueryable<T> source, string ordering, params object[] values)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
}
If you want there is already a library for dynamic linq that has a order by extension method (and others linq methods) that accepts string input for all the data types. See http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx