I'm working on this app that should show on "localhost/catalog" some data. I have a library for the models and for the services that the application might use. I am getting this error:
InvalidOperationException: The property 'Price' is not a navigation
property of entity type 'StoreAsset'. The 'Include(string)' method can
only be used with a '.' separated list of navigation property names.Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.WalkNavigations(IEntityType entityType, IReadOnlyList<string> navigationPropertyPaths, IncludeLoadTree includeLoadTree, bool shouldThrow)
Here is the code that I'm using (controller, models and view) and the service methods on bottom:
public class CatalogController : Controller
{
private IStoreAsset _assets;
public CatalogController(IStoreAsset assets)
{
_assets = assets;
}
public ActionResult Index()
{
var assetModels = _assets.GetAll();
var listingResult = assetModels
.Select(result => new AssetIndexListingModel
{
Id = result.Id,
Tipology = _assets.GetTipology(result.Id),
Size = _assets.GetSize(result.Id),
Price = decimal.Parse(_assets.GetPrice(result.Id))
});
var model = new AssetIndexModel()
{
Assets = listingResult
};
return View(model);
}
public class AssetIndexListingModel
{
public int Id { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
public string Tipology { get; set; }
public string ImageUrl { get; set; }
}
public abstract class StoreAsset
{
public int Id { get; set; }
[Required]
public Status Status { get; set; }
[Required]
public decimal Price { get; set; }
public string ImageUrl { get; set; }
}
public class Dress : StoreAsset
{
[Required]
public string Color { get; set; }
[Required]
public string Tipology { get; set; }
[Required]
public string Size { get; set; }
}
#model Models.Catalog.AssetIndexModel
<div id="assets">
<h3></h3>
<div id="assetsTable">
<table class="table table-condensed" id="catalogIndexTable">
<thead>
<tr>
<th>Size</th>
<th>Price</th>
<th>Tipology</th>
</tr>
</thead>
<tbody>
#foreach (var asset in Model.Assets)
{
<tr class="assetRow">
<td class="">
<a asp-controller="Catalog" asp-action="Detail" asp-route-id="#asset.Id">
<img src="#asset.ImageUrl" class="imageCell" />
</a>
</td>
<td class="">#asset.Price</td>
<td class="">#asset.Size</td>
<td class="">#asset.Tipology</td>
</tr>
}
</tbody>
</table>
</div>
public class StoreAssetService : IStoreAsset
{
private Context _context;
public StoreAssetService(Context context)
{
_context = context;
}
public void Add(StoreAsset newAsset)
{
_context.Add(newAsset);
_context.SaveChanges();
}
public IEnumerable<StoreAsset> GetAll()
{
return _context.StoreAssets
.Include(asset => asset.Status)
.Include(asset => asset.Price);
}
public StoreAsset GetById(int id)
{
// Return a query (same as returning GetAll().FirstOrDefault(...))
return _context.StoreAssets
.Include(assets => assets.Status)
.Include(assets => assets.Price)
// So it can return null with no problem
.FirstOrDefault(asset => asset.Id == id);
}
public StoreBranch GetCurrentLocation(int id)
{
throw new NotImplementedException();
}
// To implement and test
public string GetPrice(int id)
{
return _context.Dresses.FirstOrDefault(p => p.Id == id).Price.ToString();
}
public string GetSize(int id)
{
return _context.Dresses.FirstOrDefault(s => s.Id == id).Size;
}
public string GetStatus(int id)
{
throw new NotImplementedException();
}
public string GetTipology(int id)
{
var dress = _context.StoreAssets.OfType<Dress>()
.Where(b => b.Id == id);
// For now return other if it's not a party dress
return dress.Any() ? "Party" : "Other";
}
}
Should I use some ForeignKey attribute or change Price to a string?
Any help would be great thanks
As pointed out in the error message, the Include is for the Navigation property only.
You need to change below:
return _context.StoreAssets
.Include(asset => asset.Status)
.Include(asset => asset.Price);
To:
return _context.StoreAssets
.Include(asset => asset.Status).ToList();
Reference: https://learn.microsoft.com/en-us/ef/core/modeling/relationships#definition-of-terms
https://learn.microsoft.com/en-us/ef/core/querying/related-data
I am having yet another problem. When I go to "localhost/catalog" the page should display all columns/entries that I have in the database but it only displays one column. Is there something wrong in the foreach cicle?
Related
I have a class that represents the necessary data for creating a DynamicComponent:
public class ComponentMetadata
{
public string? Name { get; set; }
public Dictionary<string, object> Parameters { get; set; } =
new Dictionary<string, object>();
}
I have a custom table component that uses ComponentMetadata to add some dynamic component to the table:
TableTemplate.razor:
<table class="table">
<thead>
<tr>#TableHeader</tr>
</thead>
<tbody>
#foreach (var RowTemplateConfig in RowTemplateConfigs)
{
if (RowTemplateConfig.DataItem is not null)
{
<tr>
<td> #RowTemplateConfig.DataItem </td>
#if(RowTemplateConfig.ComponentMetadata is not null)
{
<td>
<DynamicComponent
Type= RowTemplateConfig.ComponentMetadata.Name
Parameters= RowTemplateConfig.ComponentMetadata.Parameters />
</td>
}
</tr>
}
}
</tbody>
</table>
#code {
public class RowTemplateConfig
{
public ComponentMetadata ComponentMetadata { get; set;}
public string DataItem { get; set; }
}
[Parameter]
public RenderFragment? TableHeader { get; set; }
[Parameter]
public List<RowTemplateConfig> RowTemplateConfigs {get; set;}
}
I have also other custom components for example Button custom component:
MyButton.razor
<button type="button"
class="#Style"
#onclick="BtnClick">
#Text
</button>
#code {
[Parameter]
public string Style { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter]
public EventCallback<object> OnBtnClick { get; set; }
private async Task BtnClick(object item)
{
await OnBtnClick.InvokeAsync(item);
}
}
I want to use my TableTemplate.razor in some razor page:
pets1.razor
#page "/pets1"
<h1>Pets</h1>
<TableTemplate RowTemplateConfigs =RowTemplateConfigs TItem =string>
<TableHeader>
<th>Name</th>
</TableHeader>
</TableTemplate>
#code {
private List<RowTemplateConfig> RowTemplateConfigs = new()
{
RowTemplateConfig = new ()
{
DataItem = "Bigglesworth",
ComponentMetadata = new()
{
Name= typeof(MyButton).
Parameters = new Dictionary<string, object>()
{
{ nameof(MyButton.Style), "btn btn-outline-success" }
{ nameof(MyButton.OnBtnClick), EventCallback.Factory.Create<object>(this, OnTestClick)},
{ nameof(MyButton.Text), "Hi"}
}
}
},
RowTemplateConfig = new ()
{
DataItem = "Saberhagen",
ComponentMetadata = new()
{
Name= typeof(MyButton).
Parameters = new Dictionary<string, object>()
{
{ nameof(MyButton.Style), "btn btn-outline-success" }
{ nameof(MyButton.OnBtnClick), EventCallback.Factory.Create<object>(this, OnTestClick)},
{ nameof(MyButton.Text), "Hi"}
}
}
},
//rest of the lest ...
}
private async Task OnTestClick(object sender)
{
// I want to catch the name of the pet here when user click on the button
await Task.Delay(20);
}
}
My Question:
When user clicks on MyButton in TableTemplate in pets1.razor I want to catch the element (name of pet) in the event OnTestClick.
Thanks and sorry for long question, I appreciate any suggestion.
You need to add a Pet Name parameter to the button component, load it and then submit the property back on the callback event.
Here's a modified version of your code (with a few typo and type fixes). Note: I code with Nullable enabled.
ComponentMetadata
public class ComponentMetadata
{
public Type Name { get; set; } = default!;
public Dictionary<string, object> Parameters { get; set; } =
new Dictionary<string, object>();
}
MyButton.razor
<button type="button" class="#Style" #onclick=BtnClick>
#Text
</button>
#code {
[Parameter] public string? Style { get; set; }
[Parameter] public string? Name { get; set; }
[Parameter] public string? Text { get; set; }
[Parameter] public EventCallback<string> OnBtnClick { get; set; }
private async Task BtnClick()
=> await OnBtnClick.InvokeAsync(this.Name);
}
And then the rest of the code rolled into my test page:
#page "/"
#foreach (var component in myDynamicComponents)
{
<DynamicComponent Type="#component.ComponentMetadata.Name" Parameters=component.ComponentMetadata.Parameters />
}
<div class="p-2 m-2">
<strong>Message:</strong> #message
</div>
#code {
private string message = string.Empty;
private RowTemplateConfig GetRowTemplate(string petName)
=> new RowTemplateConfig
{
DataItem = petName,
ComponentMetadata =
new ComponentMetadata()
{
Name = typeof(MyButton),
Parameters = new Dictionary<string, object>()
{
{ nameof(MyButton.Style), "btn btn-outline-success ms-1" },
{ nameof(MyButton.Name), petName },
{ nameof(MyButton.OnBtnClick), EventCallback.Factory.Create<string>(this, OnTestClick)},
{ nameof(MyButton.Text), $"Hi {petName}"}
}
}
};
private List<RowTemplateConfig> myDynamicComponents => new List<RowTemplateConfig> {
GetRowTemplate("Ginger"),
GetRowTemplate("Tiger"),
GetRowTemplate("Felix")
};
private async Task OnTestClick(string name)
{
await Task.Delay(20);
message = $"{name} Clicked at {DateTime.Now.ToLongTimeString()}";
}
public class RowTemplateConfig
{
public ComponentMetadata ComponentMetadata { get; set; } = default!;
public string? DataItem { get; set; }
}
}
I'm new to API and Blazor and I'm trying to follow this example (https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/crud?view=aspnetcore-5.0)
Below you can see my models and code.
Model:
public class Student {
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime EnrollmentDate { get; set; }
[JsonIgnore]
public virtual ICollection<Enrollment> Enrollments { get; set;}
}
API controller:
[HttpGet]
[Route("{id:int}")]
public async Task<ActionResult<Student>> GetStudent(int id)
{
try
{
var result = await context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.StudentId == id);
//var result = await context.Students
// .Where(s => s.StudentId == id)
// .Select(s => new
// {
// Student = s,
// Enrollment = s.Enrollments
// })
// .FirstOrDefaultAsync();
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error receiving data from database");
}
}
Student model in Blazor server
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime EnrollmentDate { get; set; }
[JsonIgnore]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
My Blazor Student base:
public class StudentDetailsBase : ComponentBase
{
[Inject]
public IEnrollmentService EnrollmentService { get; set; }
[Inject]
public IStudentService StudentService { get; set; }
public Student Student { get; set; }
public List<Enrollment> Enrollments { get; set; }
//public ICollection<Student> Student { get; set; }
[Parameter]
public string Id { get; set; }
protected override async Task OnInitializedAsync()
{
Id = Id ?? "1";
Student = await StudentService.GetStudent(int.Parse(Id));
Enrollments = (await EnrollmentService.GetEnrollmentBySID(int.Parse(Id))).ToList();
//Student = (await StudentService.GetStudent(int.Parse(Id))).ToList();
}
}
And my Student display page:
#if (Student == null)
{
<p>Loading ...</p>
}
else
{
<div>
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Enrollment Date</th>
<th>All enrollments</th>
</tr>
</thead>
<tbody>
<tr>
<td>#Student.FirstName</td>
<td>#Student.LastName</td>
<td>#Student.EnrollmentDate</td>
#foreach (var i in Student.Enrollments)
{
<td>#i.Course.Title</td>
<td>#i.Grade</td>
}
</tr>
</tbody>
</table>
</div>
}
I've tried to google the problem and i can not figure out what i'm doing wrong. My Student.Enrollments in always null. Which causes my Blazor server to throw an error.
When i test my API with Postman it's working fine.
Hopefully someone will point me in the right direction on how to solve this.
Thank you.
Kind regards.
I would like users to search through pets using a search bar or drop down lists. I tried using viewbag and asp tag helpers but I keep getting errors. Below is a picture of what i'm going for. Any help is appreciated.
Model
public class Reptile
{
public int ReptileId { get; set; }
public string Name { get; set; }
public string Age { get; set; }
[Display(Name ="Reptile's Image")]
public byte[] Image { get; set; }
[Display(Name ="Food Requirements")]
public string FoodReq { get; set; }
[Display(Name="Habitat Requiremtns")]
public string HabitatReq { get; set; }
public string Gender { get; set; }
public string Type { get; set; }
public string Size { get; set; }
public string Color { get; set; }
[Display(Name="Recent Checkup")]
public bool RecentCheckup { get; set; }
public bool Trained { get; set; }
public bool Neutered { get; set; }
public bool Declawed { get; set; }
[Display(Name = "Good With Other Reptiles")]
public bool GoodWithRept { get; set; }
[Display(Name = "Good With Kids")]
public bool GoodWithKids { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int ApplicationUserId { get; set; }
}
Controller
public async Task<IActionResult> Index(string searchString)
{
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
return View(await reptiles.ToListAsync());
}
View
<form asp-controller="Reptiles" asp-action="Index" method="get">
<div class="form-actions no-color">
<p>
Search By Type: <input type="text" name="SearchString" />
<input type="submit" value="Filter" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>
I've been trying to follow the docs here Tutorial: Add sorting, filtering, and paging - ASP.NET MVC with EF Core. Not having any luck though.
Here is a simple demo to show how to use searchstring:
Controller:
public IActionResult Index(string searchString)
{
IEnumerable<Reptile> list = new List<Reptile> { new Reptile { Type = "t1", Name= "Reptile1" }, new Reptile { Type = "t2", Name = "Reptile2" }, new Reptile { Type = "t3", Name = "Reptile3" } };
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
list = list.Where(s => s.Name.Contains(searchString));
}
return View(list);
}
View:
Find by name:
|
Back to Full List
<table>
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Type)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<input type="text" asp-for="#item.Type">
</td>
</tr>
}
</tbody>
</table>
result:
Okay, I figured out how to use select to filter the reptile page by using the data users already added to the database from the properties in the model. I had to create a view model and add the Reptile model to it.
View Model
public class ReptileGenderViewModel
{
public Reptile Reptile { get; set; }
public List<Reptile> reptiles;
public SelectList genders;
public string reptileGender { get; set; }
}
Reptile Controller
public async Task<IActionResult> Index(string searchString, string reptileGender)
{
IQueryable<string> genderQuery = from g in _context.Reptiles
orderby g.Gender
select g.Gender;
var reptiles = from r in _context.Reptiles
select r;
if (!string.IsNullOrEmpty(searchString))
{
reptiles = reptiles.Where(r => r.Type.Contains(searchString));
}
if (!string.IsNullOrEmpty(reptileGender))
{
reptiles = reptiles.Where(g => g.Gender == reptileGender);
}
var reptileGenderVM = new ReptileGenderViewModel();
reptileGenderVM.genders = new SelectList(await genderQuery.Distinct().ToListAsync());
reptileGenderVM.reptiles = await reptiles.ToListAsync();
return View(reptileGenderVM);
}
View
<select asp-for="reptileGender" asp-items="Model.genders">
<option value="">All</option>
</select>
I have clients, projects, client comments and project comments. I want to display one table grouped by client followed by all projects and for each client where there is a comment as well as for each project that has a comment display the last provided comment.
The table would have the Client Name at the top followed by the latest respective comment if provided.
It would be followed by the list of all projects for that client with their latest comment if provided.
I have the client model:
public class Client
{
public int Id { get; set; }
public string ClientName { get; set; }
public bool IsActive { get; set; }
public ICollection<ClientComment> ClientComments { get; set; }
public ICollection<Project> Projects { get; set; }
The project model:
public class Project
{
public int Id { get; set; }
public string ProjectName { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
public bool IsArchived { get; set; }
public ICollection<ProjectComment> ProjectComments { get; set; }
The client comment model:
public class ClientComment
{
public int Id { get; set; }
public int? ClientId { get; set; }
public Client Client { get; set; }
public string StatusComment { get; set; }
public DateTime LastUpdateDate { get; set; }
public ClientComment ()
{
this.LastUpdateDate = DateTime.UtcNow;
}
The project comment model:
public class ProjectComment
{
public int Id { get; set; }
public int? ProjectId { get; set; }
public Project Project { get; set; }
public string StatusComment { get; set; }
public DateTime LastUpdateDate { get; set; }
public ProjectComment ()
{
this.LastUpdateDate = DateTime.UtcNow;
}
The end result should be with their respective table headers:
ClientName1 | ClientStatusComment
ProjectName1 | ProjectStatusComment
ProjectName2 | ProjectStatusComment
ProjectName3 | ProjectStatusComment
ClientName2 | ClientStatusComment
ProjectName1 | ProjectStatusComment
ProjectName2 | ProjectStatusComment
ProjectName3 | ProjectStatusComment
You could use View Model which contains the properties you need display in the view.Refer to as follows:
ClientVM and ProjectVM
public class ClientVM
{
public string ClientName { get; set; }
public string ClientStatusComment { get; set; }
public List<ProjectVM> Projectlist { get; set; }
}
public class ProjectVM
{
public string ProjectName { get; set; }
public string ProjectStatusComment { get; set; }
}
Populate the ViewModel
public class ClientsDetailsModel : PageModel
{
private readonly MyDbContext _context;
public ClientsDetailsModel(MyDbContext context)
{
_context = context;
}
[BindProperty]
public List<ClientVM> clientVMList { get; set; }
public async Task<IActionResult> OnGet()
{
var clientlist = _context.Clients
.Include(c => c.ClientComments)
.Include(c => c.Projects)
.ThenInclude(p => p.ProjectComments).ToList();
clientVMList = new List<ClientVM>();
foreach (var item in clientlist)
{
ClientVM clientVM = new ClientVM()
{
Projectlist = new List<ProjectVM>()
};
clientVM.ClientName = item.ClientName;
if (item.ClientComments != null && item.ClientComments.Any())
{
clientVM.ClientStatusComment = item.ClientComments.OrderByDescending(cc => cc.LastUpdateDate).First().StatusComment;
}
else
{
clientVM.ClientStatusComment = "No StatusComment";
}
foreach (var projectItem in item.Projects)
{
ProjectVM projectVM = new ProjectVM();
projectVM.ProjectName = projectItem.ProjectName;
if (projectItem.ProjectComments != null && projectItem.ProjectComments.Any())
{
projectVM.ProjectStatusComment = projectItem.ProjectComments.OrderByDescending(pc => pc.LastUpdateDate).First().StatusComment;
}
else
{
projectVM.ProjectStatusComment = "No StatusComment";
}
clientVM.Projectlist.Add(projectVM);
}
clientVMList.Add(clientVM);
}
return Page();
}
}
ClientsDetails.cshtml
#page
#model MultipleEntitiesInSameView.Pages.ClientsDetailsModel
<table class="table">
<thead>
<tr>
<th>
Name
</th>
<th>
LastStatusComment
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.clientVMList)
{
<tr style="background-color:aliceblue;">
<td>
#Html.DisplayFor(modelItem => item.ClientName)
</td>
<td>
#Html.DisplayFor(modelItem => item.ClientStatusComment)
</td>
</tr>
#foreach (var projectItem in item.Projectlist)
{
<tr>
<td>
#Html.DisplayFor(modelItem => projectItem.ProjectName)
</td>
<td>
#Html.DisplayFor(modelItem => projectItem.ProjectStatusComment)
</td>
</tr>
}
}
</tbody>
</table>
4.Result :
I have create an application to upload image in database
this is my model
[Table("ImageGallery")]
public class ImageGallery
{
[Key]
public int ImageID { get; set; }
public int ImageSize { get; set; }
public string FileName { get; set; }
public byte[] ImageData { get; set; }
[Required(ErrorMessage="Please select Image File")]
public HttpPostedFileBase file { get; set; }
}
this is my database model
public class TPADB : DbContext
{
public DbSet<ImageGallery> imagegallery { get; set; }
}
this is my view
#using (Html.BeginForm("Upload", "ImageUP", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<table>
<tr>
<td>Select File : </td>
<td>
#Html.TextBoxFor(Model => Model.file, new { type="file"})
#Html.ValidationMessage("CustomError")
</td>
<td>
<input type="submit" value="Upload" />
</td>
</tr>
</table>
}
this is my controller
[HttpGet]
public ActionResult Upload()
{
return View();
}
[HttpPost]
public ActionResult Upload(ImageGallery IG)
{
IG.FileName = IG.file.FileName;
//IG.ImageSize = IG.file.ContentLength;
byte[] data = new byte[IG.file.ContentLength];
IG.file.InputStream.Read(data, 0, IG.file.ContentLength);
IG.ImageData = data;
using (TPADB db = new TPADB())
{
db.imagegallery.Add(IG);
db.SaveChanges();
}
return View();
}
but it throughs an error that
"One or more validation errors were detected during model generation:
TPA.Models.HttpPostedFileBase: : EntityType 'HttpPostedFileBase' has no key defined. Define the key for this EntityType.
HttpPostedFileBases: EntityType: EntitySet 'HttpPostedFileBases' is based on type 'HttpPostedFileBase' that has no keys defined."
Figured it out, make the following changes to the model:
public partial class ImageGallery
{
[Key]
public int ImageID { get; set; }
public int ImageSize { get; set; }
public string FileName { get; set; }
public byte[] ImageData { get; set; }
public string File
{
get
{
string mimeType = "image/png";
string base64 = Convert.ToBase64String(ImageData);
return string.Format("data:{0},{1}", mimeType, base64);
}
}
}
Then add this line to the controller:
HttpPostedFileBase File = Request.Files[0];
Replace any IG.File entry with File for example:
if (File.ContentLength > (2 * 1024 * 1024))