How to update an existing entity that has a nested list of entities? - asp.net-core

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

Related

Getting navigation properties of your entity when you return a CreatedAtRouteResult

Request:
namespace mediere_API.Requests
{
public class LocalitateRequest
{
public string Nume { get; set; }
public int JudetId { get; set; }
}
}
DTO
namespace mediere_API.Dtos
{
public class LocalitateDTO
{
public int Id { get; set; }
public string Nume { get; set; }
public JudetDTO Judet { get; set; }
}
}
Entity
using mediere_API.Dtos;
using System.ComponentModel.DataAnnotations;
namespace mediere_API.DataLayer.Entities
{
public class Localitate : BaseEntity
{
[Required]
public string Nume { get; set; }
[Required]
public int JudetId { get; set; }
public virtual Judet judet { get; set; }
public Localitate() { }
}
}
Processor method
async Task<ActionResult> ILocalitatiProcessor.AddLocalitate(LocalitateRequest localitateRequest)
{
var record = _mapper.Map<Localitate>(localitateRequest);
_unitOfWork.Localitati.Insert(record);
if (await _unitOfWork.SaveChangesAsync() == false)
{
return new BadRequestResult();
}
return new CreatedAtRouteResult("GetByIdLocalitate", new {Id = record.Id}, _mapper.Map<LocalitateDTO>(record));
}
So, I have these pieces of code.
The way I'm using my front-end, I need to have the navigation properties filled in when I get the response on the POST request.
Right now I get:
{
"id": 12777,
"nume": "test",
"judet": null
}
On the get requests it works properly, but with CreatedAtRouteResult it doesn't, and I know why, but I don't know how should I fix it.
Record doesn't have the navigation properties filled in because it is a mapping of localitateRequest (which doesn't have the navigation properties) to Localitate.
So, how should I approach this problem?
Thanks.

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.

select - keyword not working with odata, automapper and efcore

I am trying to apply the odata query to my automapper - mappings at my efcore context. Everything works as expected until I use the $select query option.
When I try to use the select keyword in the request to my odata - controller, I get the exception:
SerializationException: 'SourceSourceInjectedQuery`2' cannot be serialized using the ODataMediaTypeFormatter.
I am using the UseAsDataSource - Extension method because it was recommended here on github.
This is my oDataController:
public class StudentsController : ODataController {
private readonly SchoolContext schoolContext;
public StudentsController(SchoolContext schoolContext) {
this.schoolContext = schoolContext;
}
[EnableQuery]
public IActionResult Get() {
return Ok(
schoolContext
.Students
.UseAsDataSource()
.For<StudentVM>()
);
}
}
This is my Entity for EFCore:
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
And this is my mappingprofile for automapper:
public class StudentVM {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
}
public class StudentProfile : Profile {
public StudentProfile() {
CreateMap<Student, StudentVM>();
}
}
Do I need some specific mapping to do this?
I figured out I had a mistake in my configuration of the odataservice inside my startup.cs
private static IEdmModel GetEdmModel() {
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Student>("Students");
builder.EntitySet<Course>("Courses");
return builder.GetEdmModel();
}
I put my Entities instead of my ViewModels there. This is the fixed code:
private static IEdmModel GetEdmModel() {
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<StudentVM>("Students");
builder.EntitySet<CourseVM>("Courses");
return builder.GetEdmModel();
}
Now it's working as expected

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

ASP.Net core web API encode string to base64

I am new to .Net Core development. I have a model:
public class CoreGoal
{
[Key]
public long CoreGoalId { get; set; }
public string Title { get; set; }
public string Effect { get; set; }
public string Target_Audience { get; set; }
public string Infrastructure { get; set; }
public virtual ICollection<Image> Images { get; set; }
public CoreGoal()
{
}
}
And Image model is as following:
public class Image
{
[Key]
public long ImagelId { get; set; }
public string Base64 { get; set; }
[ForeignKey("CoreGoalId")]
public long CoreGoalId { get; set; }
public Image()
{
}
}
I am using Repository pattern. My repository:
public interface ICoreGoalRepository
{
void CreateCoreGoal(CoreGoal coreGoal);
}
public class CoreGoalRepository : ICoreGoalRepository
{
private readonly WebAPIDataContext _db;
public CoreGoalRepository(WebAPIDataContext db)
{
_db = db;
}
//Find specific
public CoreGoal Find(long key)
{
return _db.CoreGoals.FirstOrDefault(t => t.CoreGoalId == key);
}
//Add new
public void CreateCoreGoal(CoreGoal coreGoal)
{
_db.CoreGoals.Add(coreGoal);
_db.SaveChanges();
}
}
And controller:
[Route("api/[controller]")]
public class CoreGoalController : Controller
{
private readonly ICoreGoalRepository _coreGoalRepository;
//Controller
public CoreGoalController(ICoreGoalRepository coreGoalRepository) {
_coreGoalRepository = coreGoalRepository;
}
[HttpGet("{id}", Name = "GetCoreGoal")]
public IActionResult GetById(long id)
{
var item = _coreGoalRepository.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
//Create
[HttpPost]
public IActionResult Create([FromBody] CoreGoal item)
{
if (item == null)
{
return BadRequest();
}
_coreGoalRepository.CreateCoreGoal(item);
return CreatedAtRoute("GetCoreGoal", new { id = item.CoreGoalId }, item);
}
}
On POST request for CoreGoal- While creating a new CoreGoal, I would like to convert Image model's Base64 attribute from string to byte[]. I found this (https://adrientorris.github.io/aspnet-core/manage-base64-encoding.html) blogpost, but I am not sure where Am I supposed to write this piece of code.
Can someone help me?
Initially you should chage you database model to save you binary image to db (also, it's still not good idea, but let leave it for a now):
public class Image
{
[Key]
public long ImagelId { get; set; }
[NotMapped]
public string Base64 { get; set; }
public byte[] Binary {get; set;}
[ForeignKey("CoreGoalId")]
public long CoreGoalId { get; set; }
public Image()
{
}
}
next you just should convert your image inside controller:
[HttpPost]
public IActionResult Create([FromBody] CoreGoal item)
{
if (item == null)
{
return BadRequest();
}
item.Binary = Convert.FromBase64String(item.Base64);
_coreGoalRepository.CreateCoreGoal(item);
return CreatedAtRoute("GetCoreGoal", new { id = item.CoreGoalId }, item);
}
BTW:you code still not good. It's not necessary to use Repository pattern with EF core (https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/). And you should introduce two model layers: public layer and model layer. You shouldn't expose EF Core contract to outside.