How to display data from two separate tables in MVC? - c#

So, i am new to programming and i am trying to make my first .net project, and i'm stuck.
I have a database that contains table Product, it looks like this:
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
[Range(1, int.MaxValue)]
public double ProductPrice { get; set; }
public string ProductDescription { get; set; }
public string ProductImagePath { get; set; }
public int ProductColorId { get; set; }
[ForeignKey("ProductColorId")]
public virtual ProductColor ProductColor { get; set; }
public int ProductShippingOptionId { get; set; }
[ForeignKey("ProductShippingOptionId")]
public virtual ProductShippingOption ProductShippingOption { get; set; }
public int ProductConditionId { get; set; }
[ForeignKey("ProductConditionId")]
public virtual ProductCondition ProductCondition { get; set; }
Columns ProductShippingOption, ProductColor and ProductCondition are a separate tables that each contain columns for Id and Name.
When i add a product to database, i want to show details of just one product in a view, but i need to display ProductConditionName instead of ProductConditionId (for example).
What should i include in my ViewModel and my Controller so i can use it in my View?
My action in a ProductController looks like this
public IActionResult ProductTemplate(int? id)
{
ProductVM productVM = new ProductVM()
{
Product = _db.Product.Find(id),
};
if (id == 0)
{
return NotFound();
}
if (id == 0)
{
return NotFound();
}
if(productVM==null)
{
return NotFound();
}
return View(productVM);
}
Thanks!

Best and easiest way to do it is including the classes in Product:
For your ProductTemplate action :
ProductVM productVM = new ProductVM()
{
Product = _db.Product.Where(s=>s.Id == id)
.Include(s=>s.ProductColor)
.Include(s=>s.ProductShippingOption)
.Include(s=>s.ProductCondition)
.FirstOrDefault();
};
And you can call them in your .cshtml with (Let say you want ProductColor name) :
#Model.Product.ProductColor.Name
Alternatively you can add Include() to your context to take all includes defaultly.

Related

ASP.NET MVC: how to use Include tag

Can any body explain how to use Include()? I see we hae Images list in model, is it nessesary ti include them in db?
public IEnumerable<Product> GetAll(int Productstate)
{
return db.Products.Include("Tags").Include("Images").Include("Category").Include("Format").Where(pl => pl.state > 0).Where(p=>p.state >= Productstate);
}
public Product Get(Int64 id)
{
return db.Products.Include("Images").Include("Category").Where(PPR => PPR.Id == id).FirstOrDefault();
}
Product model
public Int64 Id { get; set; }
public List<ProdImage> Images { get; set; }
[MaxLength(20)]
public String Color { get; set; }
public Int64 CustomPropertyId { get; set; }
public DateTime CreateTime { get; set; }
What Include would do here, is fill the List<ProdImage> Images, with the images that have a foreign key linking to the product. Otherwise this list would be empty.
It is not required. If you want to use those images, then you should include them.

How to save tags in database c#

In frontend in Angular I created the possibility of adding tags and memes. In the back-end in the web api I would like to save the tags in the database. Using entity framework code first, I created the structure of three tables:
public class Memes
{
public int Id { get; set; }
public string MemName { get; set; }
public string UserId { get; set; }
public string Image { get; set; }
public List<Memes_tags> MemesIteam { get; set; }
}
public class HashTag
{
public int Id { get; set; }
public int MemesId { get; set; }
public string Name { get; set; }
}
public class Memes_tags
{
public int Id { get; set; }
public int MemesId { get; set; }
public int HashTagId { get; set; }
public virtual Memes Memes { get; set; }
public virtual HashTag HashTags { get; set; }
}
Then I created a method that should save memes and tags in the database:
[HttpPost]
[Route("api/Memes/AddMemes")]
public IHttpActionResult CreateMemes([FromBody] MemHashTagsViewModel createMem)
{
ApplicationDbContext db = new ApplicationDbContext();
if (createMem != null)
{
Memes mem = new Memes()
{
MemName = createMem.MemName,
Image = createMem.Image,
UserId = createMem.UserId
};
db.MemesModel.Add(mem);
foreach (var item in createMem.HashTags)
{
var hashTag = new HashTag()
{
MemesId = mem.Id,
Name = item
};
db.HashTags.Add(hashTag);
}
db.SaveChanges();
return Ok();
}
else
{
return NotFound();
}
}
Incoming data:
I have problem with the correct Memes Id record. For example, I created a mem that has Id = 4 and in the table HashTags should be 4 and in my case is 0.
Is there any other better solution for saving tags in the database? Is my solution is good?
Yeah, the thing is: since you didn't save the mem first, it doesn't have an ID when you add it to the hashtag.
If you want to do it that way, you should make HashTag a member, in form of a list (property) on the mem. Then, when creating the HashTag objects, not add a member ID. The merely add the Mem to the database, and EF will take care of the object structure.
(On my phone, will make a code example in the morning if no one beats me to it)
EDIT: Here's how i would do it:
Respectfully: Drop the Memes_tags class as their seems to be no point in having it at all. It merely works as a relation between Memes and HashTags, but that already exists.
For purposes of Best practice, at least according to MS's own EF 'get start' doc the id of the class should be named: <class_name>Id, so that has been 'corrected' as well.
public class Memes
{
public int MemesId { get; set; }
public string MemName { get; set; }
public string UserId { get; set; }
public string Image { get; set; }
public List<HashTag> HashTags { get; set; }
}
public class HashTag
{
public int HashTagId { get; set; }
public int MemesId { get; set; }
public string Name { get; set; }
public virtual Memes { get; set; }
}
Below is the modified 'CreateMemes'. The idea is, that instead of adding the ID of the 'Memes' to hashtag, we merely add the HashTags to the meme object, thus they are add to EF as well, and when the 'Memes' record is add to the database, EF will make certain to create the hashtags too.
[HttpPost]
[Route("api/Memes/AddMemes")]
public IHttpActionResult CreateMemes([FromBody] MemHashTagsViewModel createMem)
{
ApplicationDbContext db = new ApplicationDbContext();
if (createMem != null)
{
Memes mem = new Memes()
{
MemName = createMem.MemName,
Image = createMem.Image,
UserId = createMem.UserId
};
foreach (var item in createMem.HashTags)
{
var hashTag = new HashTag()
{
Name = item
};
mem.HashTags.add(hashTag);
}
db.add(mem);
db.SaveChanges();
return Ok();
}
else
{
return NotFound();
}
}
Just adding the created instance to the context entity model isn't enough db.MemesModel.Add(mem);. Id value doesn't gets generated unless you call SaveChanges() on it. This in your below code there is no Id value yet and so what you observe
var hashTag = new HashTag()
{
MemesId = mem.Id,
Name = item
};

What's the best practice for associating many-to-many table with another table?

I'm running into this scenario where I'm not sure exactly what the proper way to solve it is. I have a many-to-many table.
ProductsTable <--------> DrugsTable
ProductsTable
|----- ProductID
|----- ProductName
DrugsTable
|----- DrugID
|----- DrugStrength
|----- GCNSeqNumber
The idea is that a ProductName can have many DrugStrength or GCNSequNumber, and one GCNSeqNumber can have many ProductName.
So, to create this, I created the Model class as follows:
public class ProductsTable
{
public int ProductID { get;set; }
public string ProductName { get; set; }
public ICollection<DrugsTable> Drugs { get; set; }
public ProductsTable()
{
this.Drugs = new HashSet<DrugsTable>();
}
}
public partial class DrugsTable
{
public int DrugID { get; set; }
public string Strength { get; set; }
public string GCNSeqNo { get; set; }
public virtual ICollection<ProductsTable> Products { get; set; }
}
And then I mapped it:
modelBuilder.Entity<ProductsTable>()
.HasMany(c => c.Drugs).WithMany(i => i.Products)
.Map(t => t.MapLeftKey("ProductID")
.MapRightKey("DrugID")
.ToTable("RxTable"));
So far so good. .NET creates another table called RxTable and stores the IDs from DrugsTable and ProductsTable. Now the issue is, the RxTable doesn't have an id field. I need to have another table that contains the basic information on the claims (the date, person, as such). What's the best practice of associating another table with these many-to-many table? If I create the RxTable manually, would .NET be able to populate the table automatically? How?
Edit made in response to Luca Ghersi's response:
RxTable Rx = new RxTable();
ProductsTable Product = db.Products.Where(x => x.ProductName == productName).FirstOrDefault();
DrugsTable Drug = db.Drugs.Where(x => x.GCNSeqnumber == gcnSeqnumber).FirstOrDefault();
if (Product == null)
{
Product = AddProduct(productName);
}
if(Drug == null)
{
Drug = AddDrug(strength, gcnSeqnumber);
}
Rx = (RxTable)db.RxTable.Where(x => x.ProductId == Product.ProductId && x.DrugId == Drug.DrugId).FirstOrDefault();
if (Rx == null)
{
Rx.Drugs = Drug;
Rx.Products = Product;
db.RxTable.Add(Rx);
}
return Rx;
Actually you can simply add a autogenerated ID column to the RxTable (I mean, you can create the table by yourself on db with the ID column) an use this column as you please. EF will be able to "ignore" that column. Obviously, if you are going to put a FK on it, EF will ignore that and will simply crash if you are going to delete a record from that table without removing the linked records first.
What about creating a three key mapping table?
Like:
public class ProductsTable
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public ICollection<RxTable> RxTable { get; set; }
}
public partial class DrugsTable
{
public int DrugID { get; set; }
public string Strength { get; set; }
public string GCNSeqNo { get; set; }
public virtual ICollection<RxTable> RxTable { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public virtual ICollection<RxTable> RxTable { get; set; }
}
public class RxTable
{
[Key, Column(Order = 0)]
public int ProductID { get; set; }
[Key, Column(Order = 1)]
public int DrugID { get; set; }
[Key, Column(Order = 2)]
public int PersonID { get; set; }
public virtual ProductsTable ProductsTable { get; set; }
public virtual DrugsTable DrugsTable { get; set; }
public virtual Person Person { get; set; }
}
It's definitively harder to manage, but it's the best way to have everything under control in the code, instead on relying on out-of-EF-control tricks on SQL Server.
Hope it helps!

Entity Framework & ASP.NET MVC more complex models

I have Entity Framework model which contains reference to other Entity like
public class Product
{
[Key]
public int ProductID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual Shop Shop { get; set; }
[Required]
public double Price { get; set; }
}
I would like to create Edit View which contain Shop selector (DropDown).
By default I have created basic MVC Controller with Entity model connected, which created Edit like:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
and View does not contain Shop selector.
I have tried to add DropDown like:
#Html.DropDownListFor(product => product.Shop, (SelectList)ViewBag.Shops)
But in POST method, Shop entity is null.
How to handle that?
Create a view model to represent what you want to display
public class ProductVM
{
public int ProductID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int? ShopID { get; set; }
[Required]
public double Price { get; set; }
public SelectList ShopList { get; set; }
}
and in your controller, map your model to the view model
public ActionResult Edit(int? ID)
{
....
Product product = db.Products.Find(id);
ProductVM model = new ProductVM();
// map properties
....
// populate select list (assumes Shop has properties ID and Name)
model.ShopList = new SelectList(db.Shops, "ID", "Name");
return View(product);
}
and in your view
#model ProductVM
....
#Html.DropDownListFor(m => m.ShopID, Model.ShopList, "--Select shop--")
#Html.ValidationMessageFor(m -> m.ShopID)
this will post back the model with the selected ID of the Shop. Select controls post back single values so you cannot post back a complex object such as Shop. The POST method would be
[HttpPost]
public ActionResult Edit(ProductVM model)
{
....
}
Note you can use tools such as automapper to make mapping easier
I hope this helps.
Model for Product:
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public int ShopID { get; set; }
public double Price { get; set; }
}
Then a ViewModel for Product:
public class ProductViewModel
{
public Product Model { get; set; }
public IEnumerable<SelectListItem> Shops{ get; set; }
public ProductViewModel()
{
GetShops();
}
public void GetShops()
{
Shops = new List<SelectListItem>();
var collectionShops = GetShopsFromDatabase();
Shops.AddRange(
collectionShops.Select(
contract =>
new SelectListItem
{
Text = contract.ShopDescription,
Value = contract.ShopID.ToString()
}));
}
}
In your View:
#model ProductViewModel
....
#Html.DropDownListFor(x => x.Model.ShopID, Model.Shops, new { #title = "Please select a shop" })

Accessing dynamically added HTML controls in ASP.net MVC

I have a problem which I'm unable to solve so any help would be appreciated. I have a view in which I'm dynamically adding textboxes (depending of a value chosen in dropdownlist).
Basically, I'm entering data for the product which depending of the category it belongs to has specific attributes added to it. For example, if the product is soft dring it could have following attributes: type of packaging, flavor, volume, etc. while some other product like cell phone may have attributes like: weight, RAM, CPU clock, CPU type, etc.
This is how the database looks like:
Dynamically creating controls isn't a problem and it is done with this code:
<script type="text/javascript">
$(document).ready(function () {
$("#ProductCategoryId").change(function () {
if ($("#ProductCategoryId").val() != "") {
var options = {};
options.url = "http://localhost:59649/Product/GetProductCategoryAttributes";
options.type = "POST";
options.data = JSON.stringify({ id: $("#ProductCategoryId").val() });
options.dataType = "json";
options.contentType = "application/json";
options.success = function (productCategoryAttributes) {
$("#atributtes").empty();
for (var i = 0; i < productCategoryAttributes.length; i++) {
$("#atributi").append("<div class='editor-label'><label>" + productCategoryAttributes[i].Name + "</label></div>")
.append("<div class='editor-field'><input class='text-box single-line' id='" + productCategoryAttributes[i].Name + "' name='" + productCategoryAttributes[i].Name + "' type='text'>");
}
};
options.error = function () { alert("Error retrieving data!"); };
$.ajax(options);
}
else {
$("#atributtes").empty();
}
});
});
</script>
Method in controller that retrieves ProductAttributeCategory names depending of ProductCategoryId selected:
public JsonResult GetProductCategoryAttributes(int id)
{
var productCategoryAttributes = db.ProductCategoryAttribute
.Where(p => p.ProductCategoryId == id)
.Select(p => new { Name = p.Name, p.DisplayOrder })
.OrderBy(p => p.DisplayOrder)
.ToList();
return Json(productCategoryAttributes, JsonRequestBehavior.AllowGet);
}
Controller code for POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
db.Product.Add(product);
db.SaveChanges();
var productCategoryAttributes = db.ProductCategoryAttribute
.Where(p => p.ProductCategoryId == product.ProductCategoryId)
.OrderBy(p => p.DisplayOrder);
foreach (ProductCategoryAttribute productCategoryAttribute in productCategoryAttributes)
{
//Find HTML element that matches productCategoryAttribute.Name
ProductProductCategoryAttribute productCategoryAttributeValue = new ProductProductCategoryAttribute();
productCategoryAttributeValue.ProductId = product.ProductId;
//productCategoryAttributeValue.ProductCategoryAttributeId = Find HTML element that matches ProductCategoryAttributeID and pass its id here
//productCategoryAttributeValue.Value = Find HTML element that matches ProductCategoryAttributeID and pass its value here
db.ProductProductCategoryAttribute.Add(productCategoryAttributeValue);
db.SaveChanges();
}
return RedirectToAction("Index");
}
ViewBag.LanguageId = new SelectList(db.Language, "LanguageId", "Name", product.LanguageId);
ViewBag.ProductCategoryId = new SelectList(db.ProductCategory, "ProductCategoryId", "Name", product.ProductCategoryId);
return View(product);
}
Product model:
public partial class Product
{
public Product()
{
this.ProductPhoto = new HashSet<ProductPhoto>();
this.ProductProductCategoryAttribute = new HashSet<ProductProductCategoryAttribute>();
}
public int ProductId { get; set; }
public int LanguageId { get; set; }
public int ProductCategoryId { get; set; }
public string Name { get; set; }
public string EAN { get; set; }
public string Description { get; set; }
public virtual Language Language { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductPhoto> ProductPhoto { get; set; }
public virtual ICollection<ProductProductCategoryAttribute> ProductProductCategoryAttribute { get; set; }
}
ProductCategory model:
public partial class ProductCategory
{
public ProductCategory()
{
this.Product = new HashSet<Product>();
this.ProductCategoryAttribute = new HashSet<ProductCategoryAttribute>();
}
public int ProductCategoryId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public string PhotoLocation { get; set; }
public int DisplayOrder { get; set; }
public bool Active { get; set; }
public virtual Language Language { get; set; }
public virtual ICollection<Product> Product { get; set; }
public virtual ICollection<ProductCategoryAttribute> ProductCategoryAttribute { get; set; }
}
ProductCategoryAttribute model:
public partial class ProductCategoryAttribute
{
public ProductCategoryAttribute()
{
this.ProductProductCategoryAttribute = new HashSet<ProductProductCategoryAttribute>();
}
public int ProductCategoryAttributeId { get; set; }
public int ProductCategoryId { get; set; }
public string Name { get; set; }
public string MetaName { get; set; }
public string SymbolLocation { get; set; }
public int DisplayOrder { get; set; }
public bool Active { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductProductCategoryAttribute> ProductProductCategoryAttribute { get; set; }
}
What I can't figure out is how to get the values from those dynamically created textboxes. Pseudocode (inside the controller) would be something like this:
Get the ProductCategoryId of the product
List all the attributes belonging to the selected product category
For each attribute find the appropriate textbox inside the view and get the value entered
Save the value to the database
I'm fairly new to the MVC so my approach may be wrong. Feel free to correct me.
It's very hard to read your code so here is a simplified version that should help you. Suppose you have these two models:
public class ProductCategory
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
public class Product
{
public Product()
{
Categories = new List<ProductCategory>();
}
public int ProductId {get;set;}
public string ProductName { get; set; }
public IEnumerable<ProductCategory> Categories { get; set; }
}
If these where your models then then the name attribute of your dynamically added textbox should be:
<input type="textbox" name="Categories[i].CategoryName" />
You can safely ignore the id attribute since name attribute is enough for proper model mapping/binding. Whatever value you enter in the textbox should map into an instance of a ProductCategory's CategoryName in the list of Categories attached to the Product model...
Thank you both of you. I've found a quick and, as it seems, dirty way of accomplishing this.
Basically, you can access any HTML control by using:
var value = Request["controlName"];
That is how I was able to get values from the form. I don't know if this is the right way but it sure worked for me. Any suggestion for improving this is more than welcome.

Categories