In my WebAPI web app, I've added a varbinary(max) field to a table, and a byte[] field to the POCO (BatchCharge). This entity has a child entity (Charge).
Visual Studio 2013, Entity Framework 6, SQL Server 2014.
The data model is as follows:
public class BatchCharge
{
public int ID { get; set; }
public byte[] FileData { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
public int ID { get; set; }
public DateTime CreatedUTC { get; set; }
public decimal Amount { get; set; }
public virtual BatchCharge BatchCharge { get; set; }
}
The mapping is done on the Charge entity (child) as follows:
public class ChargeMap : EntityTypeConfiguration<Charge>
{
public ChargeMap()
{
// Primary Key
HasKey(t => t.ID);
// Table and Column Mappings
ToTable("Charge");
Property(t => t.ID).HasColumnName("ID");
Property(t => t.ID).IsRequired();
Property(t => t.CreatedUTC).HasColumnName("CreatedUTC");
Property(t => t.CreatedUTC).IsRequired();
Property(t => t.Amount).HasColumnName("Amount");
Property(t => t.Amount).IsRequired();
HasRequired(t => t.BatchCharge)
.WithMany(t => t.Charges)
.HasForeignKey(d => d.BatchChargeID);
}
}
When retrieving a list of BatchCharges using the method below:
[ActionName("GetBatchCharges")]
[HttpGet]
[Route("api/charges/batches")]
[Authorize(Roles = "Administrator")]
public HttpResponseMessage GetBatchCharges(int skip = 0,
int take = 25,
int statusFilter = 0)
{
try
{
var batchCharges = _centralDb.BatchCharges.AsQueryable();
if (statusFilter > 0)
{
batchCharges = batchCharges.Where(bc => bc.StatusID == statusFilter);
}
// Page and list.
var allBatchCharges = batchCharges.OrderByDescending(c => c.CreatedUTC);
var totalCount = allBatchCharges.Count();
var thePage = allBatchCharges.Skip(take * skip).Take(take).ToList();
// Transform and return.
var result = new
{
TotalCount = totalCount,
CurrentPage = skip,
BatchCharges = thePage.Select(c => MapperFactory.Mapper.Map<BatchCharge, BatchChargeDTO>(c)).ToList()
};
return Request.CreateResponse(HttpStatusCode.OK, result);
}
catch (Exception ex)
{
const string message = "Exception getting batch charges.";
Logger.Error(ex, message);
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new HttpError(message));
}
}
I get an OutOfMemoryException. When I break on the line above and try to view the query results, I see instead the error "The function evaluation was disabled because of an out of memory exception.". See screenshot below.
This tells me the out of memory exception is occurring inside EF6.
I've seen several related answers about how to increase process memory. I don't believe this is the problem. The test data includes six rows in the BatchCharge (parent) table, and a max file size of 27 KB!
The file is a CSV file with the child entity (Charges) information. I have no problem uploading and saving BatchCharges with file data using this model. I've also used it successfully on small numbers of Charges (child entities) per BatchCharge.
The problem started when I uploaded a BatchCharge with 600 Charges as children. As I mentioned, it's not a file size issue, because the file size is 27KB.
If there is a circular reference here, it should happen in many other similarly configured relationships I have. Or is the byte array field creating the problem in conjunction with the parent/child relationship? If so, how?
EDIT:
When I surround the code with a try/catch block, and run it step by step in a debugger, it doesn't throw an exception anywhere in my code! However, the response that reaches the browser includes a standard IIS unhandled exception page, with a server error as follows (run the snippet to see output):
body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;}
p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}
.marker {font-weight: bold; color: black;text-decoration: none;}
.version {color: gray;}
.error {margin-bottom: 10px;}
.expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }
#media screen and (max-width: 639px) {
pre { width: 440px; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
}
#media screen and (max-width: 479px) {
pre { width: 280px; }
}
<body bgcolor="white">
<span><H1>Server Error in '/' Application.<hr width=100% size=1 color=silver></H1>
<h2> <i>Exception of type 'System.OutOfMemoryException' was thrown.</i> </h2></span>
<font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">
<b> Description: </b>An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
<br><br>
<b> Exception Details: </b>System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.<br><br>
<b>Source Error:</b> <br><br>
<table width=100% bgcolor="#ffffcc">
<tr>
<td>
<code>
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.</code>
</td>
</tr>
</table>
<br>
<b>Stack Trace:</b> <br><br>
<table width=100% bgcolor="#ffffcc">
<tr>
<td>
<code><pre>
[OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.]
System.IO.MemoryStream.set_Capacity(Int32 value) +89
System.IO.MemoryStream.EnsureCapacity(Int32 value) +90
System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) +326
Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.ArteryFilter.Write(Byte[] buffer, Int32 offset, Int32 count) +106
System.Web.HttpWriter.FilterIntegrated(Boolean finalFiltering, IIS7WorkerRequest wr) +475
System.Web.HttpResponse.FilterOutput() +154
System.Web.CallFilterExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +247
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +117
</pre></code>
</td>
</tr>
</table>
<br>
<hr width=100% size=1 color=silver>
<b>Version Information:</b> Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.7.3056.0
</font>
</body>
Thanks to #IvanStoev, found the problem in the DTOs: they were referencing EF properties (highlighted below), which caused out of memory exceptions with larger data sets.
public class BatchChargeDTO
{
public int ID { get; set; }
public byte[] FileData { get; set; }
// Problem is here: type should be ChargeDTO!!
public ICollection<Charge> Charges { get; set; }
}
public class ChargeDTO
{
public int ID { get; set; }
public DateTime CreatedUTC { get; set; }
public decimal Amount { get; set; }
public int? BatchChargeID { get; set; }
// Problem is here: type should be BatchChargeDTO!!
public BatchCharge BatchCharge { get; set; }
}
Related
Im Trying to create a tree view menu, but it isnt working properly. There are 3 different stored procedures, 1st for families (parent), 2nd for categories(child), and 3rd for reports(grandchild). Families SP takes user id as paramters and seems to be working fine. Category SP takes userID and familyID and 3rd takes userID, FamilyID and CategoryID. When runnning the code it does display the families, and displays the 1 report name underneath. If I remove line of code that calls the stored procedure out of the foreach loop, it does return categories for 1 family, although it displays on all the families.
If I add a breakpoint on on the controller it will go through the loops and return the correct data, so I assume the issue is in the way it is being displayed .[Displayed data] On this screenshot is possible to see how the data is being displayed, being EPOS and RES being retuned from family SP and WebBooking which is a category
FAMILIES
¦
¦--CATEGORIES
¦
¦
¦---REPORT_NAME
Thank you In advance!
On the controller I'm calling each of the stored procedures, and if tested with a breakpoint all the data that should be returned is being returned properly. Perhaps a solution for my problem would be populate the view with the returned data from the controller, however I don't know how to do it?
Controller
foreach (var cat in model.familiesReport = context.P_Mob_Get_ReportFamilies(user).ToList())
{
Console.WriteLine(cat.FamilyName);
foreach (var name in model.CategoriesReport = context.P_Mob_Get_ReportCategories(user, cat.FamilyID).ToList())
{
Console.WriteLine(name.Category);
foreach (var test in model.namesReport = context.P_Mob_Get_ReportNames(user, cat.FamilyID, name.CategoryID).ToList())
{
Console.WriteLine(test.ReportName);
}
}
I have got a View Model that has the 3 SP_Results (Complex types in EF). On the view I am going through every single Complex type and return the name for each one, (Family, Category, Name)
View
<ul>
#foreach (var family in Model.familiesReport)
{
#family.FamilyName
<ul class="">
#foreach (var cat in Model.CategoriesReport)
{
<li href="#" class=""><a class="menu_cat"> #cat.Category</a></li>
<ul class="">
#foreach (var name in Model.namesReport)
{
<li href="#" class=""><a class="menu_name"> #name.ReportName</a></li>
}
</ul>
}
</ul>
}
</ul>
I have finally found the solution for this. Ive created a new model and instead of saving the results of the stored procedure in the complex types generated by EF saved them in this model. Each class in the model has a list where the data will be saved. as displayed bellow.
Model:
public class FamilyResultResponse
{
public List<Family> Families { get; set; }
public List<Sites> SitesGet { get; set; }
}
public class Family
{
public Nullable<int> FamilyID { get; set; }
public string FamilyName { get; set; }
public List<FamilyResultCat> FamilyCat { get; set; }
}
public class FamilyResultCat
{
public Nullable<int> familyid { get; set; }
public Nullable<int> CategoryID { get; set; }
public string Category { get; set; }
public int Allowed { get; set; }
public List<FamilyResulReportByCategory> FamilCatRep { get; set; }
}
public class FamilyResulReportByCategory
{
public Nullable<int> ReportID { get; set; }
public Nullable<int> categoryid { get; set; }
public string ReportName { get; set; }
}
Controller:
Iterate through the lists
public ActionResult Index(Guid? userID\)
{
var model = new FamilyResultResponse();
model.Families=new List<Family>();
SPMenuModel modelSPMEnu = new SPMenuModel();
foreach (var f in context.P_Mob_Get_ReportFamilies(userID).ToList())
{
var fam = new Family();
fam.FamilyID = f.FamilyID;
fam.FamilyName= f.FamilyName;
fam.FamilyCat = new List<FamilyResultCat>();
Console.WriteLine(f.FamilyName);
foreach (var c in context.P_Mob_Get_ReportCategories(userID, f.FamilyID).ToList())
{
var famcat = new FamilyResultCat();
famcat.CategoryID = c.CategoryID;
famcat.Category = c.Category;
famcat.Allowed = c.Allowed;
famcat.FamilCatRep= new List<FamilyResulReportByCategory>();
Console.WriteLine(c.Category);
foreach (var n in context.P_Mob_Get_ReportNames(userID, f.FamilyID, c.CategoryID).ToList())
{
var famcatrep = new FamilyResulReportByCategory();
famcatrep.ReportID = n.ReportID;
famcatrep.ReportName = n.ReportName;
famcatrep.categoryid = n.categoryid;
Console.WriteLine(n.ReportName);
famcat.FamilCatRep.Add(famcatrep);
}
fam.FamilyCat.Add(famcat);
}
model.Families.Add(fam);
}
modelSPMEnu.familyResult = model;
return View(model);
View
#foreach (var fam in Model.Families)
{
#fam.FamilyName
<ul class="">
#foreach (var cat in fam.FamilyCat)
{
<li href="#" class=""><a class="menu_cat"> #cat.Category </a></li>
foreach (var name in cat.FamilCatRep)
{
<ul class="">
<li class=""><a>#name.ReportName</a></li>
</ul>
}
}
</ul>
}
I'm experimenting with adding a Razor page that will display Employees in all stores that belong under the same companies subgroup identifier. If I hard code the value .Contains("ABC") in var employees then I get a list of persons in that company and its subgroups. If I try to use var currentEmpSub, then I do not get my list. I'm not sure what I need to do (add another variable? fix my LINQ query? or something else). Any suggestions would be very helpful! Once I get this I will also do one for a manager view that will show only employees under the managers' store (instead of subgroup).
Setup is a many-to-many relationship with EFCore 5.0
public class Employee
{
public int Id {get; set;}
public string Name {get; set;]
public ICollection<Tenant> Tenants {get; set;}
}
public class Tenant
{
public int Id {get; set;}
public string Name {get; set;]
public ICollection<Employee> Employees{get; set;}
}
Admin.cs
public async Task<IEnumerable<Employee>> GetAllEmployees()
{
var currentEmp = _httpContextAccessor.HttpContext?.User.GetUserId();
var currentEmpTenant = await _context.Employees
.Include(x => x.Tenants)
.Where(e => e.AspUserId == currentEmp)
.FirstAsync();
var currentEmpSub = currentEmpTenant.Tenants
.Select(x => x.SubName)
.ToString();
var employees = _context.Employees
.IgnoreQueryFilters()
.Include(t => t.Tenants)
.Where(x => x.Tenants
.Select(sub => sub.SubName)
.Contains(currentEmpSub))
.ToListAsync();
return await employees;
}
Index.cshtml
#page
#using Navrae.DataLayer.Extensions
#model Navrae.WebApp.Pages.Admin.IndexModel
#{
}
<h2>Employee List</h2>
<table class="table-hover">
<thead>
<tr>
<th>Employee Name</th>
<th>Store</th>
</tr>
</thead>
<tbody>
#foreach (var emp in #Model.Employees)
{
<tr>
<td>#emp.FullName</td>
<td>
#foreach (var comp in emp.Tenants)
{
if (emp.Tenants.Count > 1)
{
String.Join(", ", #comp.CompanyName);
}
else
{
#comp.CompanyName
}
}
</td>
</tr>
}
</tbody>
</table>
Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly IAdminView _adminView;
public IndexModel(IAdminView adminView)
{
_adminView = adminView;
}
public IEnumerable<Employee> Employees { get; set; }
public async Task OnGetAsync()
{
Employees = await _adminView.GetAllEmployees();
}
}
User #Ivan Stoev pointed out that by calling .ToString() produced a List. While it did not solve my problem it was an unintended side effect. I used .SingleOrDefault() on the end of currentEmpSub and obtained the result I was looking for.
**Note you can also use .Single() .First() .FirstOrDefault()
var currentEmpSub = currentEmpTenant.Tenants
.Select(x => x.SubName)
.SingleOrDefault();
``
It may duplicate question, i have searched all over but couldn't satisfied, so i am posting here question.
I have object as (generated from entity framework),
public partial class Usp_Sel_NotEnteredStringResources_Result
{
public string name { get; set; }
public Nullable<int> Value { get; set; }
public int StringResourceId { get; set; }
public Nullable<int> LanguageId { get; set; }
}
and view i have created as,
#{
ViewBag.Title = "Resource Entry Languagewise";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>ResourceEntry</h2>
#using (Html.BeginForm("ResourceEntry", "LangResource", FormMethod.Post))
{
<fieldset>
<table>
#for (int i = 0; i < Model.ToList().Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => Model.ElementAt(i).name)</td>
<td>#Html.TextBoxFor(m => Model.ElementAt(i).Value)</td>
#Html.HiddenFor(m => Model.ElementAt(i).LanguageId)
#Html.HiddenFor(m => Model.ElementAt(i).name)
#Html.HiddenFor(m => Model.ElementAt(i).StringResourceId)
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
And Controller as,
[HttpPost]
public ActionResult ResourceEntry(List<Usp_Sel_NotEnteredStringResources_Result> list)
{
// here getting the null value for list
// ??????
return View(list);
}
After submitting the form controller gets null value for list, what is wrong with code??
You cannot use .ElementAt(). If you inspect the html your generating you will see that the name attribute has no relationship to your model.
You model needs to implemet IList<Usp_Sel_NotEnteredStringResources_Result> and use a for loop
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].name)</td>
<td>
#Html.TextBoxFor(m => m.[i]Value)
// note you hidden inputs should be inside a td element
#Html.HiddenFor(m => m[i].LanguageId)
....
</td>
</tr>
}
Alternative if the model is IEnumerable<Usp_Sel_NotEnteredStringResources_Result>, you can use an EditorTemplate (refer [Post an HTML Table to ADO.NET DataTable for more details on how the name attributes must match your model property names, and the use of an EditorTemplate)
I'm faced with a confusing problem where in my Edit or Create action result methods, EF4 will throw a DbEntityValidationException with the inner message stating:
The field Body must be a string or
array type with a maximum length of
'128'.
The model in question looks like this:
[Table("tblArticles")]
public class Article
{
[Key]
public int ID { get; set; }
[Required(ErrorMessage="Title must be included")]
public string Title { get; set; }
[AllowHtml]
public string Body { get; set; }
[Required(ErrorMessage="Start Date must be specified")]
[Display(Name="Start Date")]
[DisplayFormat(DataFormatString="dd-mm-yyyy")]
public DateTime? StartDate { get; set; }
[Required(ErrorMessage = "End Date must be specified")]
[Display(Name = "End Date")]
public DateTime? EndDate { get; set; }
public int Priority { get; set; }
public bool Archived { get; set; }
public virtual ICollection<ArticleImage> Images { get; set; }
}
The "Body" field in the actual database is of type Text, so there's no obvious limit there. The data that I'm trying to post is this:
<p>
This is an example to confirm that new articles are looking right.</p>
<p>
<img alt="" src="http://www.google.co.nz/logos/2011/houdini11-sr.jpg"
style="width: 160px; height: 56px; float: left;" /></p>
An example of the Edit method looks like this:
[HttpPost]
public ActionResult Edit(Article article)
{
if (ModelState.IsValid)
{
try
{
articleRepository.Update(article);
}
catch (DbEntityValidationException dbevEx)
{
ErrorSignal.FromCurrentContext().Raise(dbevEx);
ModelState.AddModelError("FORM", dbevEx);
return View("Edit", article);
}
// Other exception handling happens...
}
return RedirectToAction("Index");
}
And finally, the method that actually does the grunt work is:
public void Update(T Entity)
{
dbset.Attach(Entity);
db.Entry(Entity).State = System.Data.EntityState.Modified;
db.Commit();
}
I can't see anything in code or in the database that might be causing the problem, so where else should I look?
Default length of string field in code first is 128. If you are using EF validation it will throw exception. You can extend the size by using:
[StringLength(Int32.MaxValue)]
public string Body { get; set; }
This post became somehow popular so I'm adding second approach which also works:
[MaxLength]
public string Body { get; set; }
StringLengthAttribute is from System.ComponentModel.DataAnnotations assembly and MaxLengthAttribute is from EntityFramework assembly (EF 4.1).
If you get this error using a Model First approach, check the EF Model: it may be simply that a property on the Entity you're updating has the Max Length attribute set.
For Entity Framework 4.3.1, 5.0.0 and 6.2.0, you can use IsMaxLength().
Property(m => m.Body ).IsRequired().IsMaxLength();
Configures the column to allow the maximum length supported by the database provider.
https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.configuration.stringcolumnconfiguration.ismaxlength?view=entity-framework-6.2.0
May be you have used
Property(m => m.Body ).IsRequired().HasMaxLength(128);
On your dbcontext class in OnModelCreating.
So change as per your length
For me, [MaxLength] didn't work. I have added below statement to the context.
modelBuilder.Entity<YourModel>().Property(e => e.YourColumn).HasMaxLength(4000);
I have not tried exceeding "4000" limit but I think it can be extended further. You don't have to keep it 128 as the error showed by compiler.
This question is based on using the Asp.Net MVC tabular layout display template from Phil Haack http://haacked.com/archive/2010/05/05/asp-net-mvc-tabular-display-template.aspx
The problem is in the Html.DisplayFor(m => propertyMetadata.Model). This displays the data from the property just fine, however it doesn't use any of the data annotations that may be present. I'm mostly thinking the DataType annotation here for example DataType.Date. This annotation correctly outputs a short date when used with DisplayFor, but not when the property is reflected from the ModelMetadata as in this example. (It shows a full DateTime)
<% for(int i = 0; i < Model.Count; i++) {
var itemMD = ModelMetadata.FromLambdaExpression(m => m[i], ViewData); %>
<tr>
<% foreach(var property in properties) { %>
<td>
<% var propertyMetadata = itemMD.Properties
.Single(m => m.PropertyName == property.PropertyName); %>
<%= Html.DisplayFor(m => propertyMetadata.Model) %>
</td>
<% } %>
</tr>
An example model would be an
public class TableModel
{
[UIHint("Table")]
public PeriodModel[] Periods { get; set; }
}
public class PeriodModel
{
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Date From")]
[DataType(DataType.Date)]
public DateTime DateFrom { get; set; }
[DisplayName("Date To")]
[DataType(DataType.Date)]
public DateTime DateTo { get; set; }
}
So how would this be changed to get the full metadata behaviour of DisplayFor?
Seems this is a simple one, you just have to change:
Html.DisplayFor(m => propertyMetadata.Model)
to
Html.DisplayFor(m => propertyMetadata.Model, propertyMetadata.TemplateHint)
I've been struggling with this myself for a few hours and this is the only hit I found on Google. But the answer is wrong.
propertyMetadata.TemplateHint is always null in my case.
But this works:
Html.DisplayFor(m => propertyMetadata.Model, propertyMetadata.DataTypeName)
you can check #Html.ValueFor(m => propertyMetdata.Model) instead of DisplayFor.