Automapper not mapping dependent entity with get request - asp.net-core

When I call the object model variable ,straight from the database, I receive all the needed data about the dependent entity: MenuItem.
When i use an automapper it displays the mapped MenuItem object as null.
Automapping for getAll and getAllById works.
Principle Entity: ItemCategory
DependentEntity: MenuItem
Please help.
Mapping Profile:
public class MappingProfile: Profile
{
public MappingProfile()
{
CreateMap<ItemCategory, ItemCategoryDto>();
CreateMap<ItemType, ItemTypeDto>();
CreateMap<ItemStatus, ItemStatusDto>();
CreateMap<MenuItem, MenuItemDto>();
//CreateMap<ItemCategory, ItemCategoryDto>();
//CreateMap<ItemType, ItemTypeDto>();
//CreateMap<ItemStatus, ItemStatusDto>();
}
}
ItemCategory Model class:
public partial class ItemCategory
{
public ItemCategory()
{
MenuItem = new HashSet<MenuItem>();
}
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
MenuItem Model Class:
public partial class MenuItem
{
public MenuItem()
{
Menu = new HashSet<Menu>();
MenuItemAllergy = new HashSet<MenuItemAllergy>();
MenuItemIngredient = new HashSet<MenuItemIngredient>();
MenuItemPrice = new HashSet<MenuItemPrice>();
MenuItemSpecial = new HashSet<MenuItemSpecial>();
OrderMenuItem = new HashSet<OrderMenuItem>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid ItemCategoryId { get; set; }
public Guid ItemTypeId { get; set; }
public Guid ItemStatusId { get; set; }
public virtual ItemCategory ItemCategory { get; set; }
public virtual ItemStatus ItemStatus { get; set; }
public virtual ItemType ItemType { get; set; }
public virtual ICollection<Menu> Menu { get; set; }
public virtual ICollection<MenuItemAllergy> MenuItemAllergy { get; set; }
public virtual ICollection<MenuItemIngredient> MenuItemIngredient { get; set; }
public virtual ICollection<MenuItemPrice> MenuItemPrice { get; set; }
public virtual ICollection<MenuItemSpecial> MenuItemSpecial { get; set; }
public virtual ICollection<OrderMenuItem> OrderMenuItem { get; set; }
}
ItemCategoryDTO:
public class ItemCategoryDto
{
public Guid Id { get; set; }
public string Description { get; set; }
public IEnumerable<MenuItemDto> MenuItems { get; set; }
}
MenuItemDTO:
public class MenuItemDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
ItemCategory Interface:
public interface IItemCategory: IRepositoryBase<ItemCategory>
{
IEnumerable<ItemCategory> GetAllItemCategories();
ItemCategory GetItemCategoryById(Guid categoryId);
ItemCategory GetItemCategoryWithDetails(Guid categoryId);
}
ItemCategory Repository:
public class ItemCategoryRepository: RepositoryBase<ItemCategory>, IItemCategory
{
public ItemCategoryRepository(eWaiterTestContext repositoryContext)
: base(repositoryContext)
{
}
public IEnumerable<ItemCategory> GetAllItemCategories()
{
return FindAll()
.OrderBy(ic => ic.Description)
.ToList();
}
//Can Add Find By description same way
public ItemCategory GetItemCategoryById(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.FirstOrDefault();
}
public ItemCategory GetItemCategoryWithDetails(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.Include(z=>z.MenuItem)
.FirstOrDefault();
}
}
ItemCategory Controller to return ItemCategory with related menuItems:
[HttpGet("{id}/menuItem")]
public IActionResult GetItemCategoryWithDetails(Guid id)
{
try
{
var category = _repository.ItemCategory.GetItemCategoryWithDetails(id);
if (category == null)
{
_logger.LogError($"Category with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_logger.LogInfo($"Returned owner with details for id: {id}");
var result = _mapper.Map<ItemCategoryDto>(category);
return Ok(result);
}
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetItemCategoryWithDetails action:
straight from the database{ex.Message}");
return StatusCode(500, "Internal server error");
}
}

Names of the properties must be the same, but you just skipped 's' for ItemCategory model
ItemCategory Model class:
public partial class ItemCategory {
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
ItemCategoryDto Model class:
public partial class ItemCategoryDto {
public virtual ICollection<MenuItem> MenuItems { get; set; }
}
Or you can just setup AutoMapper like this :)
public class CategoryMapper: Profile
{
public CategoryMapper()
{
CreateMap<ItemCategory, ItemCategoryDto>()
.ForMember(x => x.MenuItems, x => x.MapFrom(src => src.MenuItem));
}
}
and inject it in Startup.cs
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new GemlemMapper());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
Hope it will help, also be aware that's all properties which you need to use should be Included by the Include().

Related

Eager loading use Include got error in .Net

I'd like to display the 'ApplicationRole' Name with the 'ApplicationUser' data together using Eager loading.
here is my ApplicationUser
public class ApplicationUser : IdentityUser
{
public string? Name { get; set; }
public string? Department { get; set; }
public bool IsDeleted { get; set; }
public string RoleId { get; set; }
public virtual ApplicationRole Role { get; set; } = new ApplicationRole();
}
public class ApplicationUserDTO
{
public string Id { get; set; }
public int UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
// Foreign Key
public string RoleId { get; set; }
public virtual ApplicationRoleDTO Role { get; set; } = new ApplicationRoleDTO();
}
here is ApplicationRole
[NotMapped]
public class ApplicationRole : IdentityRole
{
public string Id { get; set; }
public string Name { get; set; }
//[InverseProperty(nameof(ApplicationUser.Role))]
public virtual ICollection<ApplicationUser> Users { get; set; } = new List<ApplicationUser>();
}
public class ApplicationRoleDTO
{
public string Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationUserDTO> Users { get; set; } = new List<ApplicationUserDTO>();
}
and here is the UserRepository
public async Task<IEnumerable<ApplicationUser>> GetAllUsersAsync()
{
//return await _dbContext.User.Where(u => u.IsDeleted == false).ToListAsync();
return await _dbContext.User.Include(u => u.Role).Where(u => u.IsDeleted == false).ToListAsync();
}
public async Task<ApplicationUser> GetUserAsync(string id)
{
return await _dbContext.User.Where(u => u.Id == id).Where(u => u.IsDeleted == false).FirstOrDefaultAsync();
}
But when I use the 'Include' keyword it show this error
When I try to use the string "Role", it also show error
public async Task<IEnumerable<ApplicationUser>> GetAllUsersAsync()
{
return await _dbContext.User.Include("Role").Where(u => u.IsDeleted == false).ToListAsync();
}
Here is the error show

How to Map DTO class to "Model" class In generic Repository

I use DTO class in API layer and I struggle to map DTO class to "model" class in generic Repository.cs in core layer.
Repository.cs :
namespace DTOMap.Core.Repository.Generic
{
public class Repository<T> : IRepository<T> where T : class
{
private DTOMapContext _context;
private DbSet<T> _table;
private IMapper _mapper;
public Repository(DTOMapContext context)
{
_context = context;
_table = _context.Set<T>();
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyMapper>();
});
_mapper = config.CreateMapper();
}
public T Add(T obj)
{
// Here how to use My Mapper to save a book or an author generically
// Sth like :
// temp = _table.Add(_mapper.Map<T>(obj)); Here I want to map Dto to model to save in the db
// return = (_mapper.Map<T>(temp)); Here I want to map Model to DTO to collect it in API
// but I can't have a reference to TDTO
throw new NotImplementedException();
}
}
}
I show you the other classes that I find useful (I only implement Add function for this example and I am a beginner in .Net) :
Author.cs
namespace DTOMap.Core.Models
{
[Table("Author")]
internal class Author
{
[Key]
public int id { get; set; }
[Required, MaxLength(255)]
public string firstName { get; set; }
[Required,MaxLength(255)]
public string lastName { get; set; }
}
}
Book.cs
namespace DTOMap.Core.Models
{
[Table("Book")]
internal class Book
{
[Key]
public int id { get; set; }
[Required,MaxLength(255)]
public string name { get; set; }
[Required]
public int authorId { get; set; }
[Required]
public Author author { get; set; }
}
}
AuthorDTO.cs
namespace DTOMap.Domain.DTO
{
public class AuthorDTO
{
public int id { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}
}
BookDTO.cs
namespace DTOMap.Domain.DTO
{
public class BookDTO
{
public int id { get; set; }
public string name { get; set; }
public int authorId { get; set; }
public AuthorDTO author { get; set; }
}
}
IRepository.cs
namespace DTOMap.Domain.Interface
{
public interface IRepository<T>
{
T Add(T obj);
}
}
MyMapper.cs
namespace DTOMap.Core
{
public class MyMapper : Profile
{
public MyMapper()
{
CreateMap<Book, BookDTO>();
CreateMap<BookDTO, Book>();
CreateMap<Author, AuthorDTO>();
CreateMap<AuthorDTO, Author>();
}
}
}
program.cs
... Some Fcts
builder.Services.AddTransient<IRepository<BookDTO>, BookRepository>();
builder.Services.AddTransient<IRepository<AuthorDTO>, AuthorRepository>();
... Some Fcts
If you need any other information, please ask me.

How to update an existing entity that has a nested list of entities?

I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.
These are my classes:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Dashboard : BaseEntity
{
public int Order { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Submenu> Submenu { get; set; }
}
public class Submenu : BaseEntity
{
public int Order { get; set; }
public bool Enabled { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public string Descriptions { get; set; }
public virtual ICollection<Action> Actions { get; set; }
public int DashboardId { get; set; }
public virtual Dashboard Dashboard { get; set; }
}
public class Action : BaseEntity
{
public string Type { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Extension { get; set; }
public virtual Submenu Submenu { get; set; }
public int SubmenuId { get; set; }
}
The one I am using to update is Dashboard, which contains the rest of the classes.
I'm trying to do it using a generic service layer and a generic repository that are defined this way:
public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
private readonly IBaseRepository<T> baseRepository;
public GenericService(IBaseRepository<T> baseRepository)
{
this.baseRepository = baseRepository;
}
public async Task Update(T entity, T attachedEntity)
{
await baseRepository.Update(entity, attachedEntity);
}
}
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly PortalContext dataContext;
private DbSet<T> DbSet { get; set; }
public BaseRepository(PortalContext context)
{
dataContext = context;
DbSet = dataContext.Set<T>();
}
public async Task Update(T entity, T attachedEntity)
{
dataContext.Entry(attachedEntity).State = EntityState.Detached;
DbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
await dataContext.SaveChangesAsync();
}
}
And, at last but no least, this is the way I am configuring everything at Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortalContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
);
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
{
return (controllerName, claimsPrincipal) =>
new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
controllerName, claimsPrincipal);
});
}
What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.
When I call the update function at the controller
public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
{
var currentDashboard = await service.Get(id);
if (currentDashboard == null)
{
return NotFound();
}
await service.Update(dashboard, currentDashboard);
return Ok();
}
I always receive the next error at the repository:
error
Is there something I am doing wrong? I have been stuck with this for a week now...
Thanks in advance and sorry for the long text, but I wanted it to be clear.
I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:
public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
IQueryable <T> query = DbSet.AsNoTracking();
if (includes != null)
{
query = includes(query);
}
return await query.FirstOrDefaultAsync(m => m.Id == id);
}

Problem with mapping two objects (with lists)

I am looking for solution my issue... Probably my Shifts class cannot be mapped.
I have entity class Worker:
public class Worker
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(200)]
public string PhotoFilePath { get; set; }
public Workplace Workplace { get; set; }
public int WorkplaceId { get; set; }
public List<Service> Services { get; set; }
public List<Shift> Shifts { get; set; }
public IEnumerable<Worker> ToList()
{
throw new NotImplementedException();
}
}
And model WorkerModel:
public int Id { get; set; }
[Required]
[DisplayName("Imię")]
public string Name { get; set; }
[DisplayName("Nazwisko")]
public string LastName { get; set; }
[Display(Name = "Zdjęcie")]
public IFormFile Photo { get; set; }
public string PhotoFilePath { get; set; }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int WorkplaceId { get; set; }
public List<ServiceModel> Services { get; set; }
public List<ShiftModel> Shifts { get; set; }
}
My default mapper profile:
//Mapping workers
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>();
And when I try map model to entity class in my action:
Worker worker = _mapper.Map<Worker>(model);
I get an issue:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
This is caused by different mapping types. Take the property Service as an example.
The resource is a type of Service.
But the destination is a type of ServiceModel.
So, they need to be converted. Here is a demo.
I create the Service and ServiceModel according to your model.
public class Service
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
public class ServiceModel
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
This is mapping relationship.
public class AutomapProfile : Profile
{
public AutomapProfile()
{
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>()
.ForMember(m => m.Services, x => x.MapFrom(y => y.Services.Select(a=>
new Service
{
serviceID=a.serviceID,
myservice=a.myservice
})));
}
}
This is the mapping method.
public IActionResult Index()
{
var model = new WorkerModel
{
Id=1,
Name="names",
//...
Services = new List<ServiceModel>
{
new ServiceModel{ serviceID=1, myservice="service1"},
new ServiceModel{ serviceID=2, myservice="service2"},
},
//...
};
Worker worker = _mapper.Map<Worker>(model);
return Ok(worker);
}
Result.

Get Error : Entity type 'Course' is defined with a single key property, but 2 values were passed to the 'DbSet.Find' method

I have a GenericRepository,Which CourseRepository inherits from it, and Entities returns a list of courses
public class CourseRepository : GenericRepositories<Course>, ICourseRepository
{
public CourseRepository(LearningSiteDbContext Context) : base(Context)
{
}
}
public class GenericRepositories<TEntity> : IGenericRepositories<TEntity> where TEntity : class, IEntity,new()
{
protected readonly LearningSiteDbContext context;
public DbSet<TEntity> Entities { get; set; }
public GenericRepositories(LearningSiteDbContext Context)
{
context = Context;
Entities = context.Set<TEntity>();
}
}
But When I run this handler In Razor Page
public async Task OnGetAsync(int Id, CancellationToken cancellationToken)
{
var selectedCourse = await courseRepository.Entities.FindAsync(Id,cancellationToken);
Model = mapper.Map<CourseEditVm>(selectedCourse);
}
I get the following error :
Entity type 'Course' is defined with a single key property, but 2 values were passed to the 'DbSet.Find' method
And this is Course entity class
public class Course
{
public Course()
{
}
public Course(DateTime CreateDate)
{
this.CreateDate = CreateDate;
}
public int Id {get;set;}
public string CourseTitle { get; set; }
public string CourseDescription { get; set; }
public decimal CoursePrice { get; set; }
public string ImageName { get; set; }
public string DemoFileName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime? UpdateDate { get; set; }
public bool IsDeleted { get; set; }
//Foreign key
public int? CourseStatusId { get; set; }
public int? CourseLevelId { get; set; }
public Guid? CustomUserId { get; set; }
public int? CourseGroupId { get; set; }
//Navigations
public CourseStatus CourseStatus { get; set; }
public CourseLevel CourseLevel { get; set; }
public CustomUser CustomUser { get; set; }
public CourseGroup CourseGroup { get; set; }
public ICollection<CourseEpisod> CourseEpisods { get; set; }
public ICollection<Keyword> Keywordkeys { get; set; }
}
And it's Course Config
class CourseConfig : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).ValueGeneratedOnAdd();
builder.Property(c => c.CourseTitle).HasMaxLength(50);
builder.Property(c => c.CourseDescription).HasMaxLength(400);
builder.Property(c => c.ImageName).HasMaxLength(255);
builder.Property(c => c.DemoFileName).HasMaxLength(255);
//Relations
builder.HasMany(c => c.Keywordkeys).WithOne(c => c.Course).HasForeignKey(c => c.CourseId);
builder.HasOne(c => c.CustomUser).WithMany(c => c.Courses).HasForeignKey(c => c.CustomUserId);
}
}
But when I run this code, I will no longer receive this error
var selectedCourse = await courseRepository.Entities.FirstOrDefaultAsync(c => c.Id == Id, cancellationToken);
What is the cause of this error? Is my code wrong? How should I fix this error?
You are using the wrong method, if you check here
FindAsync you can see that if you want to pass a cancellation token, you need to pass your keys as an array like this
.FindAsync(new object[]{id}, cancellationToken);