In my MVC5 project I use automapper to map my viewmodels to my models. But it seems that I'm doing something wrong, because not all my properties are mapped.
Here is my View Model
public class PlanboardViewModel
{
public int Id { get; set; }
[Display(Name = "Titel")]
public string Title { get; set; }
[Display(Name = "Omschrijving")]
public string Description { get; set; }
[Display(Name = "Verzoektype")]
public int AbsenceTypeId { get; set; }
public List<PlanboardEventMapViewModel> EventMap { get; set; }
public List<PlanboardEventDetail> EventDetails { get; set; }
public List<PlanboardRequest> PlanboardRequests { get; set; }
}
I use a separate class for my profiles:
public class PlanboardMappingProfile : Profile
{
protected override void Configure()
{
//CreateMap<AbsenceType, AbsenceTypeViewModel>();
//.ForAllMembers(opt => opt.Condition(s => !s.IsSourceValueNull));
CreateMap<PlanboardViewModel, Planboard>()
.ForMember(dest => dest.CSVHPlanboardEventDetail, opt => opt.MapFrom(src => src.EventDetails))
.ForMember(dest => dest.CSVHPlanboardEventMap, opt => opt.MapFrom(src => src.EventMap))
.ForMember(dest => dest.CSVHPlanboardRequest, opt => opt.MapFrom(src => src.PlanboardRequests));
CreateMap<PlanboardEventMapViewModel, PlanboardEventMap>();
}
}
In my repository I have the following code:
public int Create(PlanboardViewModel planboardViewModel)
{
try {
// map the viewmodel to the planboard model
// Map the planboards to the view model
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<PlanboardMappingProfile>();
cfg.CreateMap<PlanboardViewModel, Planboard>();
cfg.CreateMap<PlanboardEventMapViewModel, PlanboardEventMap>();
});
IMapper mapper = config.CreateMapper();
Planboard planboard = mapper.Map<Planboard>(planboardViewModel);
// Some more code here
When I submit the page and use the debugger, It shows that planboardViewModel has a list of values for PlanboardEventMapViewModel, PlanboardEventDetail, PlanboardRequest. The system is not telling me that there are any errors. When I check planboard after the mapping, it does not show any values for CSVHPlanboardEventDetail, CSVHPlanboardEventMap and CSVHPlanboardRequest.
EDIT
My PlanboardModel is created DB First EF6:
public partial class Planboard
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Planboard()
{
this.CSVHPlanboardEventDetail = new HashSet<PlanboardEventDetail>();
this.CSVHPlanboardEventMap = new HashSet<PlanboardEventMap>();
this.CSVHPlanboardRequest = new HashSet<PlanboardRequest>();
}
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int StatusID { get; set; }
public Nullable<int> AbsenceTypeID { get; set; }
public virtual AbsenceType AbsenceTypes { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardEventDetail> CSVHPlanboardEventDetail { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardEventMap> CSVHPlanboardEventMap { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PlanboardRequest> CSVHPlanboardRequest { get; set; }
}
Related
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.
How i could add records to my database , with relation many-to-many in ASP.NET MVC 6 ?
I tried like that, but it's wrong
[HttpPost]
public IActionResult addpersons(Person pers)
{
var ourdoc = db.Documents.FirstOrDefault(p => p.Documentid == docselected);
ourdoc.DocPers.Add(ourdoc);
db.SaveChanges();
return Content("s");
}
Listings of my model files :
DocContext.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.Entity;
namespace TDS_1_ASPNETMVC6.Models
{
public class DocPer
{
public int Documentid { get; set; }
public Document Document { get; set; }
public int Personid { get; set; }
public Person Person { get; set; }
}
public class DocContext : DbContext
{
public virtual DbSet<Document> Documents { get; set; }
public virtual DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DocPer>()
.HasKey(t => new
{
t.Documentid,
t.Personid
});
modelBuilder.Entity<DocPer>()
.HasOne(pt => pt.Document)
.WithMany(p => p.DocPers)
.HasForeignKey(pt => pt.Documentid);
modelBuilder.Entity<DocPer>()
.HasOne(pt => pt.Person)
.WithMany(t => t.DocPers)
.HasForeignKey(pt => pt.Personid);
}
}
}
Documents.cs
using System.Collections.Generic;
namespace TDS_1_ASPNETMVC6.Models
{
public class Document
{
public int Documentid { get; set; }
public string Name { get; set; }
public string Props { get; set; }
public List<DocPer> DocPers { get; set; }
}
}
Person.cs
using System.Collections.Generic;
namespace TDS_1_ASPNETMVC6.Models
{
public class Person
{
public int Personid { get; set; }
public string Iname { get; set; }
public string Fname { get; set; }
public string Oname { get; set; }
public string Email { get; set; }
public List<DocPer> DocPers { get; set; }
}
}
First, your controller is not checking for null; what if var ourdoc = db.Documents.FirstOrDefault(p => p.Documentid == docselected); is null? So check for null first :)
Also ourdoc.DocPers.Add(ourdoc); must be changed to ourdoc.DocPers.Add(new DocPer {Person = person});
Second, to create a many-to-many I suggest you to remove the DocPer table and map it "behind", like this:
public class Document
{
public Document()
{
Persons = new List<Person>();
}
public int Documentid { get; set; }
public string Name { get; set; }
public string Props { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
public class Person
{
public Person()
{
Documents = new List<Document>();
}
public int Personid { get; set; }
public string Iname { get; set; }
public string Fname { get; set; }
public string Oname { get; set; }
public string Email { get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>()
.HasMany<Person>(s => s.Persons)
.WithMany(c => c.Documents)
.Map(cs =>
{
cs.MapLeftKey("Documentid");
cs.MapRightKey("Personid");
cs.ToTable("DocPer");
});
}
DocPer table will be managed by the EF without exposing it. Check this site for more details. This guide will apply to EF6 only, not EF7 (which does not support many-to-many yet, for that support check this link). BTW, EF7 is still is not stable yet, so is possibile that this support will be offered in the future, but no one knows yet.
I need to map a model to a viewmodel using AutoMapper.
Model:
[Table("News")]
public class News
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public DateTime DatePostedOn { get; set; }
public int Position { get; set; }
public Category Category { get; set; }
public virtual ICollection<Picture> Pictures { get; set; }
}
[Table("Pictures")]
public class Picture
{
[Key]
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public string Filename { get; set; }
public int Type { get; set; }
public virtual ICollection<News> News { get; set; }
}
Viewmodel:
public class HomeViewModels
{
public IList<HomeMainNews> MainNews { get; private set; }
}
public class HomeMainNews
{
public int Id { get; set; }
public string Title { get; set; }
public string Date { get; set; }
public string PictureURL { get; set; }
}
Mapping:
Mapper.CreateMap<News, HomeMainNews>();
How can I map a News that have a set of Pictures, to a viewmodel with only one picture according to a certain condition "Type = 2"
Current solution:
vm.MainNews = db.News
.Select(n => new HomeMainNews {
Id = n.Id,
Date = n.DatePostedOn.ToString(),
Title = n.Title,
PictureURL = n.Pictures.Where(p => p.Type == 1).Select(p => p.Filename).FirstOrDefault().ToString()
}).ToList();
Automapper solution:
vm.MainNews = db.News.Project().To<HomeMainNews>().ToList();
Try this
Mapper.CreateMap<News, HomeMainNews>()
.ForMember(mainNew => mainNew.Date, opt => opt.MapFrom(news => news.DatePostedOn))
.ForMember(mainNew => mainNew.PictureURL, opt => opt.MapFrom(news => news.Pictures.First(pic => pic.Type == 2).Filename));
I Created a new MVC 5 project whenever i try to rename the table names as described in this question
Change table names using the new Identity system
It always breaks the foreign key relationships in the Logins, Claims, Roles tables.
I have tried to override the OnModelCreating method but in vein here is my code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//base.OnModelCreating(modelBuilder);
if (modelBuilder == null)
{
throw new ArgumentNullException("modelBuilder");
}
modelBuilder.Entity<App>().HasKey(m => new { m.AppId, m.FacebookId });
modelBuilder.Entity<IdentityUser>().ToTable("Admins");
modelBuilder.Entity<IdentityUser>().
Property(p => p.Id).HasColumnName("AdminId");
modelBuilder.Entity<Admin>()
.ToTable("Admins")
.Property(p => p.Id).HasColumnName("AdminId");
modelBuilder.Entity<IdentityUserLogin>().ToTable("Logins")
.HasKey(m => new { m.ProviderKey, m.UserId, m.LoginProvider })
.Property(m => m.UserId)
.HasColumnName("AdminId");
modelBuilder.Entity<IdentityUserRole>().ToTable("AdminRoles")
.HasKey(m => new { m.RoleId, m.UserId })
.Property(m => m.RoleId)
.HasColumnName("AdminRoleId");
modelBuilder.Entity<IdentityUserRole>().Property(m => m.UserId)
.HasColumnName("AdminId");
//modelBuilder.Entity<IdentityUserRole>().HasRequired(m => m.UserId);
//modelBuilder.Entity<IdentityUserLogin>().HasRequired(m => m.UserId);
modelBuilder.Entity<IdentityUserClaim>().ToTable("Claims")
.HasKey(m=> m.Id)
.Property(m => m.Id)
.HasColumnName("ClaimId");
modelBuilder.Entity<IdentityRole>().ToTable("Roles")
.HasKey(m => m.Id).Property(m => m.Id).HasColumnName("RoleId");
}
where the "Admin" Class is my name for the ApplicationUser Default class plus my own implementation of it
public class Admin : IdentityUser
{
[Required]
public virtual List<App> Apps { get; set; }
public bool? IsPremium { get; set; }
[DataType(DataType.Date)]
public DateTime? LastPublishDateTime { get; set; }
}
here are my other domain classes
public class App
{
[Key]
[Column("AppId", Order = 1)]
public virtual int AppId { get; set; }
[Required]
[Key]
[Column("FacebookId", Order = 2)]
public virtual string FacebookId { get; set; }
[Required]
public virtual string Secret { get; set; }
public virtual List<User> Users { get; set; }
public virtual List<Post> Posts { get; set; }
[Required]
public virtual Admin Admin { get; set; }
}
public class Post
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int PostId { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual string Link { get; set; }
public virtual string Image { get; set; }
public virtual bool IsSpecial { get; set; }
[Required]
public virtual App App { get; set; }
[Required]
public virtual DateTime? PublishDate { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int UserId { get; set; }
[MaxLength(500)]
public virtual string FacebookId { get; set; }
[MaxLength(500)]
public virtual string Token { get; set; }
[Required]
public virtual App App { get; set; }
}
Is it possible to automap UserViewModel to User?
public class User
{
[Key]
public virtual int UserId { get; set; }
public virtual string Name { get; set; }
public virtual string Surname { get; set; }
[Required]
public virtual string Username { get; set; }
[Required]
public virtual string Password { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class UserViewModel
{
public User User { get; set; }
public List<Role> AvailableRoles { get; set; }
public List<Role> AssignedRoles { get; set; }
public int[] AvailableSelected { get; set; }
public int[] AssignedSelected { get; set; }
public string SavedAssigned { get; set; }
}
AutoMapper.Mapper.CreateMap<UserViewModel, User>();
AutoMapper.Mapper.Map(model, user);
I've tried this, but it won't work. I need it for my Edit action method in the User controller to save the changes to the database.
Have you tried
AutoMapper.Mapper.CreateMap<UserViewModel, User>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.User));