.Net Core Web Api / HttpGet / About Get ICollection - c#

Firstly, sorry for my English, it is not perfect. I hope i can explain my problem.
WebApi has Movie Class like this
public class Movie
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public DateTime ReleaseDate { get; set; }
public float Price { get; set; }
public int DirectorID { get; set; }
public Director Director { get; set; }
public int GenreID { get; set; }
public Genre Genre { get; set; }
public ICollection<Actor> Actors { get; set; }
}
When I used HttpGet Movies
it happens
It cant read actors. How can i fix this?
GetMoviesQuery
namespace WebApi.Application.MovieOperations.Queries.GetMovies
{
public class GetMoviesQuery
{
private readonly IMovieStoreDbContext _context;
private readonly IMapper _mapper;
public GetMoviesQuery(IMovieStoreDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public List<GetMoviesModel> Handle()
{
var _movieList = _context.Movies.Include(x => x.Genre).Include(y => y.Director).OrderBy(z => z.Id).ToList<Movie>();
List<GetMoviesModel> mv = _mapper.Map<List<GetMoviesModel>>(_movieList);
return mv;
}
}
public class GetMoviesModel
{
public string Name { get; set; }
public string ReleaseDate { get; set; }
public string Genre { get; set; }
public string Director { get; set; }
public float Price { get; set; }
public ICollection<Actor> Actors { get; set; }
}
}
Thank you.

you have to include actors too
var _movieList = _context.Movies
.Include(x => x.Actors)
.Include(x => x.Genre)
.Include(y => y.Director)
.OrderBy(z => z.Id)
.ToList();
UPDATE:
If you only need the First and the last names of actors you will have to create a model class
public class ActorViewModel
{
public string Name { get; set; }
public string SurName { get; set; }
}
and fix GetMoviesModel accordingly
public class GetMoviesModel
{
public string Name { get; set; }
.....
public ICollection<ActorViewModel> Actors { get; set; }
}

Related

EF Core related data is null with AutoMapper

In my app one case can have many companies.
My models:
public class Case
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CaseCompany> CaseCompanies { get; set; }
}
public class CaseInput
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public IList<CaseCompanyInput> CaseCompanyInputs { get; set; }
}
public class Company
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CompanyInput
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
}
public class CaseCompany
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
public class Case Case { get; set; }
public class Company Company { get; set; }
}
public class CaseCompanyInput
{
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
public class CaseInput CaseInput { get; set; }
public class CompanyInput CompanyInput { get; set; }
}
AutoMapperProfiles.cs:
// In Startup.cs: services.AddAutoMapper(typeof(AutoMapperProfiles));
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<Case, CaseInput>().ReverseMap();
CreateMap<CaseCompany, CaseCompanyInput>().ReverseMap();
CreateMap<Company, CompanyInput>().ReverseMap();
}
}
EditCase.cs:
private readonly DBContext _dbContext;
private readonly IMapper _mapper;
[BindProperty]
public CaseInput CaseInput { get; set; }
public EditCase(DBContext dbContext, IMapper mapper)
{
_dbContext = dbContext;
_mapper = mapper;
}
public async Task<IActionResult> OnGetAsync(Guid caseId)
{
var getCase = await _dbContext.Cases.Include(x => x.CaseCompanies).ThenInclude(x => x.Company).FirstOrDefaultAsync(x => x.Id == caseId);
CaseInput = _mapper.Map<CaseInput>(getCase);
Console.WriteLine(getCase.CaseCompanies[0].Company.Name) // gets the company name
Console.WriteLine(CaseInput.CaseCompanyInputs[0].CompanyInput.Name) // CaseInput.Name is not null but CaseCompanyInputs is null
return Page();
}
I've also tried:
CaseInput = await _dbContext.Cases.ProjectTo<CaseInput>(_mapper.ConfigurationProvider).FirstOrDefaultAsync(x => x.Id == caseId);
and
CaseInput = await _dbContext.Cases.Include(x => x.CaseCompanies).ThenInclude(x => x.Company).ProjectTo<CaseInput>(_mapper.ConfigurationProvider).FirstOrDefaultAsync(x => x.Id == caseId);
with the same result: CaseCompanyInputs is null.
My best guess is that it's an error in the relations of the input models, but I just can't see it. I believe I've followed the naming conventions to make the relations right.
Any help would be appreciated.
For automapping to work, you have to rename ur destination property name the same as the source property name
public class CaseInput
{
public Guid Id { get; set; }
public string Name { get; set; }
//public IList<CaseCompanyInput> CaseCompanyInputs { get; set; }
public IList<CaseCompanyInput> CaseCompanies { get; set; }
}
public class CaseCompanyInput
{
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
//public CaseInput CaseInput { get; set; }
public CaseInput Case { get; set; }
//public CompanyInput CompanyInput { get; set; }
public CompanyInput Company { get; set; }
}
If you don't want to change the property names, change ur configuration to the following.
CreateMap<Case, CaseInput>()
.ForMember(dest => dest.CaseCompanyInputs, opt => opt.MapFrom(src => src.CaseCompanies));
CreateMap<CaseCompany, CaseCompanyInput>()
.ForMember(dest => dest.CaseInput, opt => opt.MapFrom(src => src.Case))
.ForMember(dest => dest.CompanyInput, opt => opt.MapFrom(src => src.Company));

How to fix Entity Framework Core "Argument types do not match" with Linq Select projection

When attempting a straight forward projection using Entity Framework Core and Linq, I am getting an "Argument types do not match" exception.
I have looked into possible causes and have narrowed it down to the Select that is causing the error (see below). There is a GitHub issue describing a similar situation with simple types and optional navigation entities, but none of the suggested solutions have worked for me. It is not a nullable type and I have tried casting or using Value on any child properties. I have also tried setting the relationship to required in the DbContext which isn't exactly ideal.
Here is the Linq query in the repository:
return await _dashboardContext.PresetDashboardConfig
.Where(config => config.DashboardTypeId == dashboardType && config.OrganisationType = organisationType)
.GroupBy(config => config.GroupId)
.Select(config => new DashboardConfigDTO
{
DashboardType = config.First().DashboardTypeId,
OrganisationId = organisationId,
WidgetGroups = config.Select(group => new WidgetGroupDTO
{
Id = group.Id,
Name = group.GroupName,
TabOrder = group.TabOrder,
// Problem Select below:
Widgets = group.Widgets.Select(widget => new WidgetConfigDTO
{
IndicatorId = widget.IndicatorId,
ScopeId = widget.ScopeId.ToString(),
ParentScopeId = widget.ParentScopeId.ToString(),
WidgetType = widget.WidgetType,
WidgetSize = widget.WidgetSize,
Order = widget.Order
})
})
})
.SingleOrDefaultAsync();
And the entities:
public class DashboardConfig
{
public int Id { get; set; }
public int DashboardTypeId { get; set; }
public int OrganisationType {get; set; }
public int GroupId { get; set; }
public string GroupName { get; set; }
public int TabOrder { get; set; }
}
public class PresetDashboardConfig : DashboardConfig
{
public ICollection<PresetWidgetConfig> Widgets { get; set; }
}
public class WidgetConfig
{
public int Id { get; set; }
public int IndicatorId { get; set; }
public long ScopeId { get; set; }
public long? ParentScopeId { get; set; }
public int WidgetType { get; set; }
public int WidgetSize { get; set; }
public int Order { get; set; }
}
public class PresetWidgetConfig : WidgetConfig
{
public int PresetDashboardConfigId { get; set; }
}
And finally, the DbContext ModelBuilder:
modelBuilder.Entity<PresetDashboardConfig>(entity =>
{
entity.Property(e => e.GroupName)
.HasMaxLength(32)
.IsUnicode(false);
entity.HasMany(e => e.Widgets)
.WithOne();
});
Below are the DTO classes as per Henk's comment:
public class DashboardConfigDTO
{
public int DashboardType { get; set; }
public int OrganisationId { get; set; }
public IEnumerable<WidgetGroupDTO> WidgetGroups { get; set; }
}
public class WidgetGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int TabOrder { get; set; }
public IEnumerable<WidgetConfigDTO> Widgets { get; set; }
}
public class WidgetConfigDTO
{
public int IndicatorId { get; set; }
public string ScopeId { get; set; }
public string ParentScopeId { get; set; }
public int WidgetType { get; set; }
public int WidgetSize { get; set; }
public int Order { get; set; }
}

One-to-one relationship does not carry both classes

I'm using the Entity Framework to persist and retrieve some information, I have a one-to-one relationship.
Product and Category, where a Product has a Category and a Category may have several Products.
I have the following structure
I created the two entities and made the relationship, but when I retrieve this information it brings me the information of the products however the category comes as null
Produto.cs
public class Produto
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
Categoria.cs
public class Categoria
{
[Key]
[ForeignKey("categoriaid")]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual Produto produto { get; set; }
}
ProdutoContexto.cs
public class ProdutoContexto : DbContext
{
public ProdutoContexto(DbContextOptions<ProdutoContexto> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Produto>()
.HasOne(a => a.categoria)
.WithOne(b => b.produto)
.HasForeignKey<Categoria>(b => b.id);
}
public DbSet<Produto> Produtos { get; set; }
}
CategoriaContexto.cs
public class CategoriaContexto : DbContext
{
public CategoriaContexto(DbContextOptions<CategoriaContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
}
When I run the function to retrieve the information the following json is returned
[{"id":1,"descricao":"Coca-Cola","observacao":"Coca-Cola Gelada","status":1,"valorVenda":5.50,"categoria":null}]
My Query is:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.ToListAsync();
}
Note that the category is null, how can it be done in such a way that the category is already loaded?
Category may have several Products.
Then its not one-to-one, instead its one-to-many and your model classes should be as follows:
public class Categoria
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual ICollection<Produto> produtos { get; set; }
}
public class Produto
{
[Key]
public int id { get; set; }
[ForeignKey("categoria")]
public int categoriaId {get; set;}
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
And you don't need any FluentAPI configuration. So remove the modelBuilder.Entity<Produto>() configuration. And you also don't need two different DbContext for Produto and Categoria separately. Instead make your DbContext as follows:
public class ApplicationDbContexto : DbContext
{
public ApplicationDbContexto(DbContextOptions<ApplicationDbContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
public DbSet<Produto> Produtos { get; set; }
}
And your query should be as follows:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.Include(p => p.categoria).ToListAsync();
}

Automapper many to many mapping confusion

I have many to many relationship tables such as "User & Notification & UserNotification" and their entities, view models also.
There is only a difference between ViewModel and Entity classes. HasRead property is inside NotificationViewModel. How Can I map this entities to view models? I could not achieve this for HasRead property.
What I did so far is,
Mapping Configuration:
CreateMap<Notification, NotificationViewModel>();
CreateMap<User, UserViewModel>().ForMember(dest => dest.Notifications, map => map.MapFrom(src => src.UserNotification.Select(x => x.Notification)));
Notification class:
public class Notification : IEntityBase
{
public Notification()
{
this.UserNotification = new HashSet<UserNotification>();
}
public int Id { get; set; }
public string Header { get; set; }
public string Content { get; set; }
public System.DateTime CreateTime { get; set; }
public bool Status { get; set; }
public virtual ICollection<UserNotification> UserNotification { get; set; }
}
User Class
public class User : IEntityBase
{
public User()
{
this.UserNotification = new HashSet<UserNotification>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool Status { get; set; }
public virtual ICollection<UserNotification> UserNotification { get; set; }
}
UserNotification class:
public class UserNotification : IEntityBase
{
public int Id { get; set; }
public int UserId { get; set; }
public int NotificationId { get; set; }
public bool HasRead { get; set; }
public virtual Notification Notification { get; set; }
public virtual User User { get; set; }
}
UserViewModel class
public class UserViewModel : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool Status { get; set; }
public IList<NotificationViewModel> Notifications { get; set; }
}
NotificationViewModel class
public class NotificationViewModel
{
public int Id { get; set; }
public string Header { get; set; }
public string Content { get; set; }
public System.DateTime CreateTime { get; set; }
public bool Status { get; set; }
public bool HasRead { get; set; } // this is the difference
}
In order to fix up the HasRead, maybe you can utilize the AfterMap(Action<TSource, TDestination> afterFunction) function. It's not as elegant as the rest of automapper, but it might work.
CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Notifications, map => map.MapFrom(src => src.UserNotification.Select(x => x.Notification)))
.AfterMap((src, dest) =>
{
foreach (var notificationVM in dest.Notifications)
{
notificationVM.HasRead = src.UserNotification.Where(x => x.NotificationId == notificationVM.Id).Select(x => x.HasRead).FirstOrDefault();
}
});

Automapper configuration complex mappings

I hav been using automapper for sometime trying figure out how to handle different situation. I came across below situation and need some help figuring out the best approach. Below are my EF related classes;
public sealed class Invoice
{
public int InvoiceID { get; set; }
public DateTime InvoiceDate { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public double? DiscountAmt { get; set; }
public Transaction InvoiceTransaction { get; set; }
public int TransactionID { get; set; }
}
public sealed class Transaction
{
public Transaction()
{
this.TransactionItems = new List<TransactionDetail>();
}
public int TransactionID { get; set; }
public DateTime TransactionDate { get; set; }
public DateTime TransactionLogDate { get; set; }
public TransactionType TransactionType { get; set; }
public IList<TransactionDetail> TransactionItems { get; set; }
public Invoice RefferingInvoice { get; set; }
public string Remarks { get; set; }
}
public sealed class TransactionDetail
{
public int TransactionID { get; set; }
public string ProductItemcode { get; set; }
public Product Product { get; set; }
public double Qty
{
get
{
return Math.Abs(this.StockChangeQty);
}
}
public double StockChangeQty { get; set; }
public double? UnitPrice { get; set; }
}
public sealed class Product
{
public Product()
{
this.StockTransactions = new List<TransactionDetail>();
}
public string ItemCode { get; set; }
public string ProductName { get; set; }
public string Manufacturer { get; set; }
public double UnitPrice { get; set; }
public IList<TransactionDetail> StockTransactions { get; set; }
public double Qty
{
get
{
if (this.StockTransactions.Count == 0)
{
return 0;
}
else
{
return this.StockTransactions.Sum(x => x.StockChangeQty);
}
}
}
public bool Discontinued { get; set; }
}
These are my view model classes;
public class InvoiceReportViewModel
{
public InvoiceReportViewModel()
{
LineItems = new List<InvoiceReportLineItemViewModel>();
}
public int InvoiceID { get; set; }
public DateTime InvoiceDate { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public double? DiscountAmt { get; set; }
public string Remarks { get; set; }
public string StringInvoiceNo
{
get
{
return InvoiceID.ToString("########");
}
}
public IList<InvoiceReportLineItemViewModel> LineItems { get; set; }
}
public class InvoiceReportLineItemViewModel
{
public string ItemCode { get; set; }
public string ProductName { get; set; }
public string Manufacturer { get; set; }
public double? UnitPrice { get; set; }
public double Qty { get; set; }
public double LineTotal
{
get
{
if (UnitPrice.HasValue)
{
return UnitPrice.Value * Qty;
}
else
{
return 0;
}
}
}
}
My requirement is to convert the Invoice EF object to InvoiceReportViewModel object.
To do this I need to setup the profile. This is where I run into a problem; as it's not straight forward. The only way I see this done is by create my own Resolver by extending TypeConverter and manually doing the conversion by overriding ConvertCore method.
If there another way of getting this done (something with less work)???
Also I feel I could Map TransactionDetails EF class to InvoiceReportLineItemViewModel class by using the Mapper.CreateMap()..ForMember(...
But how can I use the mapper to convert it within the ConvertCore method?
Thanks in advance
In your case I do not see any requirements to use any custom converters.
You can convert Invoice EF object to InvoiceReportViewModel using simple Mapper.CreateMap like following:
public class InvoiceProfile: Profile
{
protected override void Configure()
{
Mapper.CreateMap<Invoice, InvoiceReportViewModel>()
.ForMember(c => c.CustomerName, op => op.MapFrom(v => v.CustomerName))
.ForMember(c => c.DiscountAmt, op => op.MapFrom(v => v.DiscountAmt))
.ForMember(c => c.InvoiceDate, op => op.MapFrom(v => v.InvoiceDate))
.ForMember(c => c.LineItems, op => op.MapFrom(v => v.InvoiceTransaction.TransactionItems));
Mapper.CreateMap<TransactionDetail, InvoiceReportLineItemViewModel>()
.ForMember(c => c.ProductName, op => op.MapFrom(v => v.Product.ProductName))
.ForMember(c => c.Qty, op => op.MapFrom(v => v.Qty))
//and so on;
}
}
Do not forget to register "InvoiceProfile"

Categories