I have two tables product and Images. They have a one-to-many relationship. I want to display product name and all its related images in the view, I am using repository pattern. I am new to MVC and Linq please help. Thanks in advance.
Here is my code....
public partial class tbl_Product
{
public int pro_id { get; set; }
public string pro_name { get; set; }
public string pro_desc { get; set; }
public string pro_model { get; set; }
public string pro_dimensions { get; set; }
public Nullable<int> pro_UnitsInStock { get; set; }
public Nullable<double> pro_price { get; set; }
public Nullable<double> pro_oldprice { get; set; }
public virtual ICollection<tbl_Images> tbl_Images { get; set; }
}
ProductRepository class:
public ProductDetail GetProductByID(int id)
{
var product = this.storeDB.tbl_Product.Where(x => x.pro_id == id).FirstOrDefault();
return product;
}
Just add an Include clause to load the related images:
public ProductDetail GetProductByID(int id)
{
var product = storeDB.tbl_Product
.Where(x => x.pro_id == id)
.Include(p => p.tbl_Images)
.FirstOrDefault();
return product;
}
Related
I am trying to create a view to display product information and its related images. I am using repository pattern,
please see the code below, I will really appreciate your help
public class ProductDetail
{
public int pro_id { get; set; }
public string pro_name { get; set; }
public string pro_model { get; set; }
public string pro_Dimensions { get; set; }
public string pro_imageTitle { get; set; }
public string pro_image { get; set; }
public string pro_desc { get; set; }
public Nullable<double> pro_price { get; set; }
public int pro_UnitsInStock { get; set; }
public Nullable<double> pro_oldprice { get; set; }
public virtual ICollection<Images> tbl_Images { get; set; }
}
public class Images
{
public int ImageID { get; set; }
public int productID { get; set; }
public string ImageTitle { get; set; }
public string ImagePath { get; set; }
}
public class ProductDetailRepository : IProductDetail
{
private readonly WebStoreEntities storeDB;
public ProductDetailRepository() { }
public ProductDetailRepository(WebStoreEntities _storeDB)
{
this.storeDB = _storeDB;
}
public ProductDetail GetProductByID(int id)
{
var prod = storeDB.tbl_Product
.Where(x => x.pro_id == id)
.Include(p => p.tbl_Images)
.FirstOrDefault();
return prod; (Here it says, cannot implicitly convert type tbl_product to productdetail (this is where i need help))
}
tbl_product is from the EDMX model.
}
now, i am stcuk in this method, all i want is to return the product info and related images to the controller and then view.
You basically just need to convert your tbl_Product that you get from the database into the ProductDetail you want to return:
public ProductDetail GetProductByID(int id)
{
var prod = storeDB.tbl_Product
.Where(x => x.pro_id == id)
.Include(p => p.tbl_Images)
.FirstOrDefault();
if (prod == null)
{
return null;
}
ProductDetail result = new ProductDetail
{
// I'm just *GUESSING* here since you haven't showed
// the tbl_product class, so I don't know what the
// properties on that class are called, really...
pro_id = prod.Id,
pro_name = prod.Name
// and so on for all the properties
}
return result;
}
I am using Entity Framework with OData to get data from my mysql database but I don't want to expose database entites to the user, so I've created some DTO's and map them with Automapper.
My Problem is that everything works fine except loading entities with $expand.
There are 2 Entities with 2 DTO's (in my project the dto's and domain models do not look the same, this is only for better reading):
public partial class Product
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<ProductPrice> ProductPrices { get; set; }
}
public class ProductDTO
{
[Key]
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual ICollection<ProductPriceDTO> ProductPrices { get; set; }
public virtual CategoryDTO Category { get; set; }
}
public partial class Category
{
public int Id { get; set; }
public string Title { get; set; }
}
public class CategoryDTO
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}
public partial class ProductPrice
{
public string VendorId { get; set; }
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual Product Product { get; set; }
public virtual Vendor Vendor { get; set; }
}
public class ProductPriceDTO
{
[Key]
[ForeignKey("Vendor")]
public string VendorId { get; set; }
[Key]
[ForeignKey("Product")]
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual VendorDTO Vendor { get; set; }
public virtual ProductDTO Product { get; set; }
}
The models are created the following way:
public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.Namespace = "Functions";
//category
builder.EntitySet<CategoryDTO>("Categories").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
//product
builder.EntitySet<ProductDTO>("Products").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
return builder.GetEdmModel();
//productprice
builder.EntitySet<ProductPriceDTO>("ProductPrices").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
}
Automapper profile:
public AutoMapperProfile()
{
CreateMap<Product, ProductDTO>()
.ForMember(dto => dto.Category, conf => conf.AllowNull())
.ForMember(dto => dto.ProductPrices, dest => dest.MapFrom(x => x.ProductPrices))
.ForMember(dto => dto.ProductPrices, dest => dest.ExplicitExpansion())
.ForMember(dto => dto.ProductPrices, conf => conf.AllowNull());
CreateMap<ProductPrice, ProductPriceDTO>()
.ForMember(dto => dto.Product, conf => conf.AllowNull())
.ForMember(dto => dto.Vendor, conf => conf.AllowNull());
}
Controller:
[Authorize]
[ODataRoutePrefix("Products")]
public class ProductsController : BaseODataController
{
private readonly IProductService ProductService;
private readonly IProductPriceService ProductPriceService;
public ProductsController(IMapper mapper, IProductService productService, IProductPriceService productPriceService) : base(mapper)
{
ProductService = productService;
ProductPriceService = productPriceService;
}
[AllowAnonymous]
[EnableQuery]
public IQueryable<ProductDTO> Get(ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts();
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
[AllowAnonymous]
[EnableQuery]
[ODataRoute("({key})")]
public IQueryable<ProductDTO> Get([FromODataUri] string key, ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts().Where(x => x.Id.Equals(key));
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
}
As I mentioned above every query works fine ($select, $filter, $orderBy, $count).
But when I call the following:
https://localhost:44376/odata/Products('631794')?$expand=Category
I get:
{"#odata.context":"https://localhost:44376/odata/$metadata#Products","value":[
as response.
In the output of Visual Studio there is a message:
No coercion operator is defined between types 'System.Int16' and 'System.Boolean'.
I think there must be something wrong with the Automapper profile. As I read somewhere .ProjectTo() with include parameters creates a Select to get the related data from the navigation property. I thought it is enough to create the relation with [ForeignKey] in the DTO.
My entities:
public class Product : Base.BaseEntity
{
public Product()
{
this.Colors = new HashSet<Color>();
}
[StringLength(50)]
public string ProductName { get; set; }
public int CategoryID { get; set; }
[StringLength(100)]
public string StockFinishes { get; set; }
[StringLength(50)]
public string Guarantee { get; set; }
[StringLength(50)]
public string Installation { get; set; }
public virtual ProductEntity.Category OwnerCategory { get; set; }
public virtual IList<VariationEntity.Variation> Variations { get; set; }
public virtual ICollection<ProductEntity.Color> Colors { get; set; }
}
public class Color : Base.BaseEntity
{
public Color()
{
this.Products = new HashSet<Product>();
}
[StringLength(50)]
public string ColorName { get; set; }
public virtual ICollection<ProductEntity.Product> Products { get; set; }
}
My controller:
[HttpPost]
public ActionResult NewProduct(Models.DTO.ProductDTO.ProductVM productmodel)
{
if (ModelState.IsValid)
{
DATA.Models.ORM.Entity.ProductEntity.Product productentity = new DATA.Models.ORM.Entity.ProductEntity.Product();
productentity.ProductName = productmodel.ProductName;
productentity.CreatedBy = User.UserId;
productentity.CategoryID = productmodel.CategoryID;
productentity.StockFinishes = productmodel.StockFinishes;
productentity.Guarantee = productmodel.Guarantee;
productentity.Installation = productmodel.Installation;
rpproduct.Insert(productentity);
rpproduct.SaveChanges();
if (productmodel.SelectedColors != null)
{
foreach (var colorId in productmodel.SelectedColors)
{
DATA.Models.ORM.Entity.ProductEntity.Color color = rpcolor.FirstOrDefault(x => x.Id == colorId);
productentity.Colors.Add(color);
}
db.SaveChanges();
}
return RedirectToAction("ProductList");
}
else
{
ViewBag.Error = "An error occurred while adding a new product";
return View();
}
}
There is no error, but product colors are not inserted into the database. I can't do it with repository.
How can I add product colors with repository or without repository?
Sorry for bad English :(
I prefer to manage many to many relations myself not Entity Framework.
In my view you need another table to store colors of products with columns ColorId and ProductId. Then there should be a DbSet on your DbContext.
After that you can save a new entity ProductColors which stores ColorId and ProductId. Your entities Color and Product can have a reference to this table, not to Color and Product table.
public class ProductColor : Base.BaseEntity
{
public ProductColor()
{
}
public int ColorId { get; set; }
public virtual Color Color { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
IQueryable<Product> product = objContext.Set<Product>().Include(p =>
p.Categories.Name).Where(p => p.Id == 2);
As per the current view, I'm getting an error. It says add other model with their properties. i.e. to include Category model and corresponding Name property.
#model IEnumerable<>crudOneToMany.Models.Product>
using viewmodel, is it possible to join two tables?
View
Error
A specified Include path is not valid. The EntityType 'crudOneToMany.Models.Category' does not declare a navigation property with the name 'Name'.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ProductDBContext : DbContext
{
public ProductDBContext()
: base("ProductDBContext")
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasRequired(o => o.Categories).WithMany(o => o.Products).HasForeignKey(o => o.CategoryId);
base.OnModelCreating(modelBuilder);
}
}
Your problem is here:
.Include(p => p.Categories.Name)
Instead you should write .Include(p => p.Categories)
This means that in output there will be loaded Categories navigation collection to product.
Name is simple string property (is not navigation property so it should not be included)
Here is the proposed ViewModel for you.
ProductViewModel.cs
public class ProductViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public Category Category { get; set; }
public ICollection<Category> Categories { get; set; }
}
It's really helpful if anyone could explain me how to create a mapping (associated)table for one to many relationship using Fluent API.`
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public List<Image> Images { get; set; }
}
public class Image
{
public int ID { get; set; }
public string Name { get; set; }
public List<Category> Category{ get; set; }
The mapping table should contain CategoryID and ImageID.
The solution should be something similar to this. (This is for many to many)
modelBuilder.Entity<Category>()
.HasMany(c => c.Images).WithMany(i => i.Categories)
.Map(t => t.MapLeftKey("CategoryID")
.MapRightKey("ImageID")
.ToTable("CategoryImage"));
I want Fluent API to create new mapping table for the below relationship.
public class Category
{
public List<Image> Images{get; set;}
}
public class Image
{
public Category Category{ get; set; }
}
Adding NewTable:
modelBuilder
.Entity<NewTable>()
.HasRequired(_ => _.Category)
.WithMany()
.HasForeignKey(_ => _.CategoryId);
modelBuilder
.Entity<Image>()
.HasRequired(_ => _.NewTable)
.WithMany(_ => _.Images)
.HasForeignKey(_ => _.NewTableId)
public class NewTable
{
public int ID { get; set; }
public int CategoryId { get; set; }
public List<Image> Images { get; set; }
public virtual Category Category { get; set; }
}
public class Image
{
public int ID { get; set; }
public string Name { get; set; }
public List<Category> Categories { get; set; }
public int NewTableId { get; set; }
public virtual NewTable NewTable { get; set; }
}