Missing Map from 'Object' to 'Object' when using ProjectTo<> - asp.net-core

I am working on Web API and have mappings to convert from Domain models to Application models using AutoMapper(10.1.1)
I have registered AutoMapper in Application layer
public static IServiceCollection ConfigureAppServices(this IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
//services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<StartUp>())
// services.AddControllersWithViews();
return services;
}
Used this class in Web API startup.cs
services.ConfigureAppServices();
I have this generic interface for Mapping called IMapFrom
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
Mapping is from 'JobModel' to 'JobDetailsDto'
public class JobDetailsDto: IMapFrom<JobModel>
{
public JobDetailsDto()
{
}
public string Id { get; set; }
public string Name { get; set; }
public string JobType { get; set; }
public string ShipBranch { get; set; }
public string ProjectManager { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<JobModel, JobDetailsDto>()
.ForMember(d => d.ShipBranch, opt => opt.MapFrom(s => s.PriceBranch))
.ForMember(d => d.ProjectManager, opt => opt.MapFrom(s => s.BidderId));
}
}
JobModel.cs
public class JobModel
{
public JobModel()
{
}
public int Id { get; set; }
public DateTime BidDate { get; set; }
public string JobType { get; set; }
public DateTime CreatedDate { get; set; }
public string City { get; set; }
public string State { get; set; }
public string BidderId { get; set; }
public string PriceBranch { get; set; }
public string Name { get; set; }
}
I am using MediaTr to handle API requests
public async Task<JobDetailsDto> Handle(GetJobDetailsQuery request, CancellationToken cancellationToken)
{
var vm = await _context.JobModels
.Where(e => e.Id == request.Id)
.ProjectTo<JobDetailsDto>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync(cancellationToken);
return vm;
}
When I perform 'GET' which fetches data in 'JobModel' object and converts to 'JobDetailsDto' throws below error
System.InvalidOperationException: Missing map from Domain.Entities.JobModel to Application.ApplicationBusiness.Job.Queries.JobDetailsDto. Create using CreateMap<JobModel, JobDetailsDto>.
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps, TypeMap& typeMap) at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps)
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request)
at AutoMapper.Internal.LockingConcurrentDictionary2.<>c__DisplayClass2_1.<.ctor>b__1() at System.Lazy1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) at System.Lazy1.CreateValue()
at System.Lazy1.get_Value() at AutoMapper.Internal.LockingConcurrentDictionary2.GetOrAdd(TKey key)
at AutoMapper.QueryableExtensions.ExpressionBuilder.GetMapExpression(Type sourceType, Type destinationType, Object parameters, MemberInfo[] membersToExpand)
at AutoMapper.QueryableExtensions.ProjectionExpression.ToCore(Type destinationType, Object parameters, IEnumerable1 memberPathsToExpand) at AutoMapper.QueryableExtensions.ProjectionExpression.ToCore[TResult](Object parameters, IEnumerable1 memberPathsToExpand)
at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](Object parameters, Expression1[] membersToExpand) at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression1[] membersToExpand)
at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Expression`1[] membersToExpand)

Try this.
public JobDetailsDto()
{
Mapping();
}
public void Mapping()
{
CreateMap<JobModel, JobDetailsDto>()
.ForMember(d => d.ShipBranch, opt => opt.MapFrom(s => s.PriceBranch))
.ForMember(d => d.ProjectManager, opt => opt.MapFrom(s => s.BidderId));
}
Another solution is instead of writing mapping inside dto, you should create MappingProfile, where you will add your Mappings. Then in your service you should add it in constructor
_mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile<MappingProfile>(); });

Related

Invalid Column name when adding Migration

I am using .NET 5 and getting this error when I try to add a column to my database. The only fixes I found so far was to set the right startup project, but since I dont have two projects I dont know how to fix it.
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Invalid column name 'EspStartTime'.
Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
ApplicationDbContext
internal class TaskEntityTypeConfiguration : IEntityTypeConfiguration<Task>
{
public void Configure(EntityTypeBuilder<Task> builder)
{
builder.Property(e => e.Description)
.HasConversion(
xml => xml.ToString(),
xml => xml != null ? XElement.Parse(xml) : null)
.HasColumnType("xml");
}
}
internal class StudentAssignedTaskEntityTypeConfiguration : IEntityTypeConfiguration<StudentAssignment>
{
public void Configure(EntityTypeBuilder<StudentAssignment> builder)
{
builder.Property(e => e.FeedbackXml)
.HasConversion(
xml => xml.ToString(),
xml => xml != null ? XElement.Parse(xml) : null)
.HasColumnType("xml");
builder.Property(e => e.StudentSubmissionAttachmentsXml)
.HasConversion(
xml => xml.ToString(),
xml => xml != null ? XElement.Parse(xml) : null)
.HasColumnType("xml");
builder.Property(e => e.StudentSubmissionXml)
.HasConversion(
xml => xml.ToString(),
xml => xml != null ? XElement.Parse(xml) : null)
.HasColumnType("xml");
}
}
public class ApplicationDbContext : IdentityApplicationDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
//this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public static readonly ILoggerFactory Factory
= LoggerFactory.Create(builder => { builder.AddConsole(); });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(Factory);
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var relationship in builder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
relationship.DeleteBehavior = DeleteBehavior.Restrict;
builder.Entity<ApplicationUser>().HasMany(x => x.TeacherGroups).WithOne(x => x.Teacher).OnDelete(DeleteBehavior.SetNull);
builder.Entity<ApplicationUser>().HasMany(x => x.StudentGroups).WithMany(x => x.Students);
builder.Entity<SchoolClassStudentTerm>().HasKey(x => new { x.StudentId, x.SchoolClassId, x.TermId });
builder.ApplyConfiguration(new TaskEntityTypeConfiguration());
builder.ApplyConfiguration(new StudentAssignedTaskEntityTypeConfiguration());
builder.Entity<GroupAssignment>().HasOne(x => x.ClassroomGroup).WithMany(x => x.GroupAssignments).OnDelete(DeleteBehavior.Cascade);
builder.Entity<GroupAssignment>().HasMany(x => x.StudentAssignments).WithOne(x => x.GroupAssignment).OnDelete(DeleteBehavior.Cascade);
builder.Entity<GroupAssignment>().HasOne(x => x.Task).WithMany(x => x.GroupAssignments).OnDelete(DeleteBehavior.Cascade);
builder.Entity<SchoolClassStudentTerm>().HasOne(x => x.Student).WithMany(x => x.SchoolClassStudentTerms).OnDelete(DeleteBehavior.Cascade);
builder.Entity<StudentAssignment>().HasOne(x => x.Student).WithMany().OnDelete(DeleteBehavior.SetNull);
builder.Entity<GroupAssignment>().HasOne(x => x.Creator).WithMany(x => x.CreatedGroupAssignments).OnDelete(DeleteBehavior.SetNull);
builder.Entity<SchoolClass>().HasMany(x => x.SchoolClassStudentTerms).WithOne(x => x.SchoolClass);
// see https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x?view=aspnetcore-5.0#add-identityuser-poco-navigation-properties
builder.Entity<ApplicationUser>().HasMany(x => x.UserRoles).WithOne(x => x.User)
.HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
}
public DbSet<SchoolClassStudentTerm> SchoolClassStudentTerms { get; set; }
public DbSet<Term> Terms { get; set; }
public DbSet<StudentAssignment> StudentAssignments { get; set; }
public DbSet<ClassroomGroup> ClassroomsGroups { get; set; }
public DbSet<SchoolClass> SchoolClasses { get; set; }
public DbSet<GroupAssignment> GroupAssignments { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Subject> Subjects { get; set; }
IdentityDbContext
// see https://learn.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-5.0
public abstract class IdentityApplicationDbContext : IdentityDbContext<
ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public IdentityApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
});
}
}
ApplicationUser
public class ApplicationUser : IdentityUser
{
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
public virtual ICollection<ClassroomGroup> TeacherGroups { get; set; }
public virtual ICollection<ClassroomGroup> StudentGroups { get; set; }
public virtual ICollection<Task> CreatedTasks { get; set; }
public virtual ICollection<SchoolClassStudentTerm> SchoolClassStudentTerms { get; set; }
public virtual ICollection<GroupAssignment> CreatedGroupAssignments { get; set; }
public bool Disabled { get; set; }
public String FullName { get; set; }
public DateTime? EspStartTime { get; set; }
}
ClassroomGroup
public class ClassroomGroup
{
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual SchoolClass SchoolClass { get; set; }
public int SchoolClassId { get; set; }
public String TeacherId { get; set; }
public virtual ApplicationUser Teacher { get; set; }
public int Id { get; set; }
public String Title { get; set; }
public virtual ICollection<ApplicationUser> Students { get; set; }
public int TermId { get; set; }
public virtual Term Term { get; set; }
public virtual ICollection<GroupAssignment> GroupAssignments { get; set; }
}

Automapper mapping to properties from a nested object [duplicate]

I am trying to map a result I get from database and I have the following models
public class ClientTest
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientTestDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientDbItem
{
public ClientTest Client { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Value { get; set; }
}
and the following mapping
CreateMap<ClientTest, ClientTestDto>();
CreateMap<ClientDbItem, ClientTestDto>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.Client));
When I run the software I get
Custom configuration for members is only supported for top-level
individual members on a type
Why is that happening If I am creating the config for ClientTest and ClientTestDto first?
There's a special API for that, IncludeMembers. See here.
This error might also arise when you try to map the top-level element to a constructor:
.ForMember(dest => dest, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
Instead of mapping the top-level element MEMBER to the constructor (which works fine):
.ForMember(dest => dest.MyProperty, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
To avoid this, use .ForCtorParam
Example:
CreateMap<Source, DestinationWithConstructor>()
.ForCtorParam("code", opt => opt.MapFrom(src => src.Prop3))
.ForCtorParam("text", opt => opt.MapFrom(src => src.Prop4))

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);
}

ef core may to many update with extra filed

My mode class:
public class AccountCustomer
{
public bool IsMain { get; set; }
public int AccountId { get; set; }
public Account Account { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
public class Account
{
public int Id { get; set; }
public strig No{ get; set; }
..other fields
public ICollection<AccountCustomer> AccCustomer { get; set; } = new List<AccountCustomer>();
}
public class Customer
{
public int Id { get; set; }
public strig Name{ get; set; }
..other fields
public ICollection<AccountCustomer> AccCustomer { get; set; } = new List<AccountCustomer>();
}
I found soluton here and i have implemented same later i found that it is for without extra column
Many to many ef core updaate
Please let meknow how to do update... Am using latest ef 5 preview
My code :
_context.Set<AccountCustomer>().UpdateLinks(ac => ac.AccountId, account.Id,
ac => ac.CustomerId, account.AccCustomer .Select(ac => ac.CustomerId));
Codes of ManyToMany Update Extensions
Remove the unselected item and add new item to list.
public static class Extensions
{
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.ExceptThat(newItems, getKey));
db.Set<T>().AddRange(newItems.ExceptThat(currentItems, getKey));
}
private static IEnumerable<T> ExceptThat<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
}
Codes of Update ViewModel
The update view pass it to action via request.
public class AccountCustomerVM
{
public bool IsMain { get; set; }
public Account Account { get; set; }
public List<int> Customers { get; set; }
}
Codes of Update Action
[HttpPut]
public IActionResult Update(AccountCustomerVM accountCustomerVM)
{
var model = _context.Accounts.Include(x => x.AccCustomer).FirstOrDefault(x => x.Id == accountCustomerVM.Account.Id);
_context.TryUpdateManyToMany(model.AccCustomer, accountCustomerVM.Customers
.Select(x => new AccountCustomer
{
IsMain = accountCustomerVM.IsMain,
CustomerId = x,
AccountId = accountCustomerVM.Account.Id
}), x => x.CustomerId);
_context.SaveChanges();
return Ok();
}

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);