Dynamic DBSet lookup and query - asp.net-mvc

I have a partial result view that takes in the name of the table and a value for a particular column to query. I read the DBContext API and found that Set(Type) should return a DBSet that you can do CRUD operations on. I don't know how exactly to query the DBSet without a PK since the user don't know the PK to look up.
May be using Classic ADO would be easier?
EDIT: I figure out how to use DbSet.SQLQuery function but have no clue to store the results. I inspected the element in debugger and the SQLQuery does work as it found all the rows inside the table.
public class SF1DB : DbContext
{
//List of table names that feeds a DropDownList
public DbSet<tablelist> tables { get; set; }
//Data table
public DbSet<dataTable1> dataTable1 { get; set; }
public DbSet<dataTable2> dataTable2 { get; set; }
//...list of other tables
}
public PartialViewResult GetFeatures(String tablelist, String[] countyfp)
{
String type = "MvcApplication1.Models." + tablelist;
Type dbType = Type.GetType(type);
DbSet set = _db.Set(dbType);
String sql = "select * from " + tablelist;
//How do I store the result in a variable?
set.SqlQuery(sql);
return PartialView();
}

I figured it out by creating a List that have the same type as the DbSet that the user selected. Then I use the SQLQuery's GetEnumerator method and iterate thru the result and add to the new list. Finally, pass the list to the partial view.
public PartialViewResult GetFeatures(String tablelist, String[] countyfp)
{
String type = "MvcApplication1.Models." + tablelist;
Type dbType = Type.GetType(type);
DbSet set = _db.Set(dbType);
String sql = "select * from " + tablelist + " where ";
Type listType = typeof(List<>).MakeGenericType(dbType);
IList list = (IList)Activator.CreateInstance(listType);
for (int i = 0; i < countyfp.Length; i++)
{
sql += "cntyidfp like '%" + countyfp[i] + "'";
if (i < (countyfp.Length - 1))
{
sql += " or ";
}
}
IEnumerator result = set.SqlQuery(sql).GetEnumerator();
while (result.MoveNext())
{
list.Add(result.Current);
}
return PartialView(list);
}

Related

ASP.NET CORE MVC Loading table page takes too long

I have a table with orders(around 8000 records),
The table takes a few seconds to load.
The reason for that is because one of the field shown on the page is being retrieved from another table
(returnProductName).
when removing this function the table loads fast.
When loading the records I'm using Skip and Take but when retrieving the product name i'm iterating all the Orders since if the user wants to search by product name it will show all results with this product.
The product table is not big (around 70 records)
I can't figure out why the function will make the page load so slow.
I know i can just add the product name column to the table and populate it when ever adding new orders,
but this doesn't sounds right,
Can anyone tell me the reason for this delay?
returnProductName Function :
public string returnProductName(int productId)
{
return (_unitOfWork.Product.GetAll().Where(q => q.Id == productId).Select(q =>
q.ProductName)).FirstOrDefault();
}
Function that loads the page data:
[HttpPost]
public ActionResult GetList()
{
//Server Side parameters
int start = Convert.ToInt32(Request.Form["start"].FirstOrDefault());
int length = Convert.ToInt32(Request.Form["length"].FirstOrDefault());
string searchValue = Request.Form["search[value]"].FirstOrDefault();
string sortColumnName = Request.Form["columns["+Request.Form["order[0][column]"]+"][name]"].FirstOrDefault();
string sortDirection = Request.Form["order[0][dir]"].FirstOrDefault();
List<Order> orderList = new List<Order>();
orderList = _unitOfWork.Order.GetAll().ToList();//Working Fast
int totalRows = orderList.Count;
foreach (Order order in orderList)
{
order.ProductName = returnProductName(order.ProductId);
}
if (!string.IsNullOrEmpty(searchValue))
{
orderList = orderList.Where(x => x.FullAddress.ToLower().Contains(searchValue.ToLower())
x.Id.ToString().Contains(searchValue.ToLower()) ||
x.OrderStatus.ToLower().Contains(searchValue.ToLower()) ||
x.ProductName.ToLower().Contains(searchValue.ToLower()) |||
x.Quantity.ToString().Contains(searchValue.ToLower()) ||
x.Cost.ToString().Contains(searchValue.ToLower()) ||
(!string.IsNullOrEmpty(x.TrackingNumber) && x.TrackingNumber.ToString().Contains(searchValue.ToLower()))
).ToList<Order>();
}
int totalRowsAfterFiltering = orderList.Count;
orderList = orderList.Skip(start).Take(length).ToList<Order>();
return Json(new { data = orderList, draw = Request.Form["draw"], recordsTotal = totalRows ,
recordsFiltered = totalRowsAfterFiltering});
}
I would perhaps consider updating the GetAll() method or creating another one which returns a dictionary.
In this case GetAllById() and then updating returnProductName which I would rename to GetProductName():
// Or whatever your type is
public Dictionary<int, List<Product>> GetAllById()
{
// your code..
return data
.GroupBy(x => x.Id)
.ToDictionary(x => x.Key, x => x.ToList());
}
public string GetProductName(int productId)
{
var products = _unitOfWork.Product.GetAllById();
return products[productId].FirstOrDefault(q => q.ProductName);
}

File name changes after opening downloaded excel file in .Net C# MVC

I am trying to export data as Excel in my C#.Net MVC Application. I have used return file() in Actionresult. The file is returned and downloaded successfully.
But there is error while opening file and the file names gets changed when it is opened.
Downloaded file name is ExportFilterCRMdoctorRequest.xls but after opening it changes to Book1.
code for Exporting file:
public ActionResult ExportFilterCRMdoctorRequest()
{
var stream = new MemoryStream();
var serializer = new XmlSerializer(typeof(List<CDRFilterCRMDoctorRequest>));
//We load the data
List<CDRFilterCRMDoctorRequest> data = (List<CDRFilterCRMDoctorRequest>)Session["filterCRMRequestList"]; //Retriving data from Session
//We turn it into an XML and save it in the memory
serializer.Serialize(stream, data);
stream.Position = 0;
//We return the XML from the memory as a .xls file
return File(stream, "application/vnd.ms-excel", "ExportFilterCRMdoctorRequest.xls");
}
This is called Extension Hardening. Execute steps to avoid this error.
Open your Registry (Start -> Run -> regedit.exe) Navigate to
HKEY_CURRENT_USER\SOFTWARE\MICROSOFT\OFFICE\12.0\EXCEL\SECURITY
Right click in the right window and choose New -> DWORD Type
“ExtensionHardening” as the name (without the quotes)
Verify that the data has the value “0″
NOTE
There is one thing that has to be borne in mind when serializing in
XML. XML is not Excel’s standard format and it has to open the file as
XML data. This means that when opening the file it will issue a couple
of warnings which are more of a nuisance than anything else.
Back to your original Query : Replicated your issue and below is the fix
Sample Class
public class StudentModel
{
public string Name { get; set; }
public string Address { get; set; }
public string Class { get; set; }
public string Section { get; set; }
}
Sample Data
private List<StudentModel> StudentData()
{
List<StudentModel> objstudentmodel = new List<StudentModel>();
objstudentmodel.Add(new StudentModel { Name = "Name1", Class = "1",
Address = "Address1", Section = "A" });
objstudentmodel.Add(new StudentModel { Name = "Name2", Class = "2",
Address = "Address2", Section = "A" });
return objstudentmodel;
}
Action Method
public ActionResult Index()
{
List<StudentModel> objstudent = new List<StudentModel>();
objstudent = StudentData();
StringBuilder sb = new StringBuilder();
sb.Append("<table border='" + "1px" + "'b>");
//code section for creating header column
sb.Append("<tr>");
sb.Append("<td><b><font size=2>NAME</font></b></td>");
sb.Append("<td><b><font size=2>CLASS</font></b></td>");
sb.Append("<td><b><font size=2>ADDRESS</font></b></td>");
sb.Append("<td><b><font size=2>SECTION</font></b></td>");
sb.Append("</tr>");
//code for creating excel data
foreach (StudentModel item in objstudent)
{
sb.Append("<tr>");
sb.Append("<td><font>" + item.Name.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Class.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Address.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Section.ToString() + "</font></td>");
sb.Append("</tr>");
}
sb.Append("</table>");
HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=student_" +
DateTime.Now.Year.ToString() + ".xls");
this.Response.ContentType = "application/vnd.ms-excel";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
return File(buffer, "application/vnd.ms-excel");
}

Web API + ODataQueryOptions + $top or $skip is causing a SqlException

This code has been simplified for this example.
The query is actually returned from a service, which is why I would prefer to write the method this way.
[HttpGet]
public PageResult<ExceptionLog> Logging(ODataQueryOptions<ExceptionLog> options)
{
var query = from o in _exceptionLoggingService.entities.ExceptionDatas
select new ExceptionLog {
ExceptionDataId = o.ExceptionDataId,
SiteId = o.SiteId,
ExceptionDateTime = o.ExceptionDateTime,
StatusCode = o.StatusCode,
Url = o.Url,
ExceptionType = o.ExceptionType,
ExceptionMessage = o.ExceptionMessage,
Exception = o.Exception,
RequestData = o.RequestData
};
var results = options.ApplyTo(query) as IEnumerable<ExceptionLog>;
var count = results.LongCount();
return new PageResult<ExceptionLog>(results, Request.GetNextPageLink(), count);
}
The above code errors on "results.LongCount()" with the following Exception:
SqlException: The text, ntext, and image data types cannot be compared or sorted, except when using IS NULL or LIKE operator.
It appears that I'm getting an exception with when trying to page, like this "$top=2". Everything works fine if my querystring is like this "$filter=ExceptionDataId gt 100".
Since ExceptionData (the Entity) matches ExceptionLog (business model) I can do something like this as a workaround:
[HttpGet]
public PageResult<ExceptionLog> Logging(ODataQueryOptions<ExceptionData> options)
{
var query = from o in _exceptionLoggingService.entities.ExceptionDatas
orderby o.ExceptionDateTime descending
select o;
var results = from o in options.ApplyTo(query) as IEnumerable<ExceptionData>
select new ExceptionLog {
ExceptionDataId = o.ExceptionDataId,
SiteId = o.SiteId,
ExceptionDateTime = o.ExceptionDateTime,
StatusCode = o.StatusCode,
Url = o.Url,
ExceptionType = o.ExceptionType,
ExceptionMessage = o.ExceptionMessage,
Exception = o.Exception,
RequestData = o.RequestData
};
return new PageResult<ExceptionLog>(results, Request.GetNextPageLink(), results.LongCount());
}
But this doesn't completely work for me because it's a little hackish and I can't use the service's method which already gives me an IQueryable.
Another thing to note, is if the Logging method is converted to IQueryable, everything works correctly. But I need to return the Count with the query so I have to return a PageResult.
This is the workaround I'm using. I only apply the filter from the ODataQueryOptions and I manually apply the Top and Skip.
First I created some extension methods:
using System;
using System.Collections.Generic;
using System.Linq;
namespace System.Web.Http.OData.Query
{
public static class ODataQuerySettingsExtensions
{
public static IEnumerable<T> ApplyFilter<T>(this IQueryable<T> query, ODataQueryOptions<T> options)
{
if (options.Filter == null)
{
return query;
}
return options.Filter.ApplyTo(query, new ODataQuerySettings()) as IEnumerable<T>;
}
public static IEnumerable<T> ApplyTopAndTake<T>(this IEnumerable<T> query, ODataQueryOptions<T> options)
{
IEnumerable<T> value = query;
if (options.Top != null)
{
value = value.Take(options.Top.Value);
}
if (options.Skip != null)
{
value = value.Skip(options.Skip.Value);
}
return value;
}
}
}
Now my method looks like this:
[HttpGet]
public PageResult<ExceptionLog> Logging(ODataQueryOptions<ExceptionLog> options)
{
// GetLogs returns an IQueryable<ExceptionLog> as seen in Question above.
var query = _exceptionLoggingService.GetLogs()
.ApplyFilter(options);
var count = query.Count();
var results = query.ApplyTopAndTake(options);
return new PageResult<ExceptionLog>(results, Request.GetNextPageLink(), count);
}

How do i use LinqToExcel to get cell values contained in the excel file

I am using this code to retrieve the receipient name and receipient number but the recpt.receipient_name and recpt.receipient_number are null.
The excel table is of this format
Name Number
andrew 1223
james 12223
dave 454545
//select names from the excel file with specified sheet name
var receipients = from n in messages.Worksheet<BulkSmsModel>(sheetName)
select n;
foreach (var recpt in receipients)
{
BulkSms newRecpt = new BulkSms();
if (recpt.receipient_number.Equals("") == true || recpt.receipient_number == 0)
{
continue;
}
newRecpt.receipient_name = recpt.receipient_name;
newRecpt.receipient_number = Int32.Parse(recpt.receipient_number.ToString());
IBsmsRepo.insertReceipient(newRecpt);
IBsmsRepo.save();
}
After some research I found a way to get the value from excel file with LinqToExcel and get the list of all Cells. Check this MVC C# Sample.
using LinqToExcel;
using Syncfusion.Olap.Reports;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.OleDb;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace YourProject.Controllers
{
public class DefaultController : Controller
{
// GET: Default1
public ActionResult Index()
{
return View();
}
public dynamic UploadExcel(HttpPostedFileBase FileUpload)
{
string PathToyurDirectory = ConfigurationManager.AppSettings["Path"].ToString();//This can be in Anywhere, but you have to create a variable in WebConfig AppSettings like this <add key="Path" value="~/Doc/"/> This directory in my case is inside App whereI upload the files here, and I Delete it after use it ;
if (FileUpload.ContentType == "application/vnd.ms-excel"
|| FileUpload.ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|| FileUpload.ContentType == "application/vnd.ms-excel.sheet.binary.macroEnabled.12"
)
{
string filename = FileUpload.FileName;
string PathToExcelFile = Server.MapPath(PathToyurDirectory + filename);
// string targetpath = ;
FileUpload.SaveAs(PathToyurDirectory);
var connectionString = string.Empty;
string sheetName = string.Empty;
yourmodel db = new yourmodel();
Employee Employee = New Employee(); //This is your class no matter What.
try
{
if (filename.EndsWith(".xls") || filename.EndsWith(".csv") || filename.EndsWith(".xlsx") || filename.EndsWith(".xlsb"))
{
connectionString = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0 Xml;HDR=YES;IMEX=1\";", PathToExcelFile);
sheetName = GetTableName(connectionString);
}
var ExcelFile = new ExcelQueryFactory(PathToExcelFile);
var Data = ExcelFile.Worksheet(sheetName).ToList();
foreach (var item in Data)
{
//if yout excel file does not meet the class estructure.
Employee = new Employee
{
Name = item[1].ToString(),
LastName = item[2].ToString(),
Address = item[3].ToString(),
Phone = item[4].ToString(),
CelPghone = item[5].ToString()
};
db.Employee.Add(Employee);
db.SaveChanges();
}
}
catch (Exception)
{
throw;
}
}
return View();
}
private string GetTableName(string connectionString)
{
// You can return all Sheets for a Dropdown if you want to, for me, I just want the first one;
OleDbConnection oledbconn = new OleDbConnection(connectionString);
oledbconn.Open();
// Get the data table containg the schema guid.
var dt = oledbconn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
var sheet = dt.Rows[0]["TABLE_NAME"].ToString().Replace("$", string.Empty);
oledbconn.Close();
return sheet;
}
}
}
Since the property names on the BulkSmsModel class do not correlate directly to the column names in the spreadsheet, you will need to map the property names to the column names.
Assuming messages is the ExcelQueryFactory object, this would be the code.
var messages = new ExcelQueryFactory("excelFileName");
messages.AddMapping<BulkSmsModel>(x => x.receipient_name, "Name");
messages.AddMapping<BulkSmsModel>(x => x.receipient_number, "Number");
var receipients = from n in messages.Worksheet<BulkSmsModel>(sheetName)
select n;

MVC3 DataAnnotationsExtensions error using numeric attribute

I've installed Scott's Kirkland DataAnnotationsExtensions.
In my model I have:
[Numeric]
public double expectedcost { get; set; }
And in my View:
#Html.EditorFor(model => model.expectedcost)
Now, when the page tries to render I get the following error:
Validation type names in unobtrusive
client validation rules must be
unique. The following validation type
was seen more than once: number
Any ideas why I'm getting the error ?
The quick answer is simply remove the attribute
[Numeric]
The longer explanation is that by design, validation already adds a data-val-number because it's of type double. By adding a Numeric you are duplicating the validation.
this works:
[Numeric]
public string expectedcost { get; set; }
because the variable is of type string and you are adding the Numeric attribute.
Hope this helps
I basically had the same problem and I managed to solve it with the following piece of code: (As answered here: ASP.NET MVC - "Validation type names must be unique.")
using System;
using System.Web.Mvc;
And the ValidationRule:
public class RequiredIfValidationRule : ModelClientValidationRule
{
private const string Chars = "abcdefghijklmnopqrstuvwxyz";
public RequiredIfValidationRule(string errorMessage, string reqVal,
string otherProperties, string otherValues, int count)
{
var c = "";
if (count > 0)
{
var p = 0;
while (count / Math.Pow(Chars.Length, p) > Chars.Length)
p++;
while (p > 0)
{
var i = (int)(count / Math.Pow(Chars.Length, p));
c += Chars[Math.Max(i, 1) - 1];
count = count - (int)(i * Math.Pow(Chars.Length, p));
p--;
}
var ip = Math.Max(Math.Min((count) % Chars.Length, Chars.Length - 1), 0);
c += Chars[ip];
}
ErrorMessage = errorMessage;
// The following line is where i used the unique part of the name
// that was generated above.
ValidationType = "requiredif"+c;
ValidationParameters.Add("reqval", reqVal);
ValidationParameters.Add("others", otherProperties);
ValidationParameters.Add("values", otherValues);
}
}
I hope this helps.

Resources