How to use automap with object with nested dto? - c#

I am new to Automap, and I am trying to filter out the result. I want to know how to map nested dtos.
Post Entity:
public class Post
{
public Author? Author { get; set; }
[Required] [Key] public int Id { get; set; }
[Required] public string Title { get; set; }
[Required] public string Description { get; set; }
[Required] public string Body { get; set; }
}
PostRead: (dto)
public class PostRead
{
public DateTime Created { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Description { get; set; }
// Author would work but I want only the AuthorRead data
public AuthorRead Author;
}
Author Entity
public class Author
{
[Key] [Required] public int Id { get; set; }
[Required] public string Name { get; set; }
public IEnumerable<Post> Posts { get; set; }
}
AuthorRead.cs (dto)
public class AuthorRead
{
public int Id { get; set; }
public string Name { get; set; }
}
Technically if I use Author Entity in PostRead, it works but it'll give the list of the posts the Author has, and i want only the information that is in the AuthorRead (so the API response doesn't send the list of posts of the Author itself).
how I can map the object of type Author to the type AuthorRead in the PostRead?
Errors:
AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.
Mapping types:
Object -> PostRead
System.Object -> OhMyBlogAPI.Models.PostRead
at lambda_method22(Closure , Object , PostRead , ResolutionContext )
at OhMyBlogAPI.Tests.AutomapTests.MockPost_MapsTo_PostRead() in
What I tried , and searching a lot.
CreateMap<Post, PostRead>()
.ForMember(m
=> m.Author, o
=> o.MapFrom<Author, AuthorRead>("Author"));
And profiles (each line represent relevant profiles content):
CreateMap<Post, PostRead>();
CreateMap<Author, AuthorRead>();

My bad, the code works.
I misconfigured something in the Unit testing. I am really sorry.

Related

How to map recipes with ingredients using AutoMapper

I have following RecipeModel, IngredientModel and RecipePartModel classes which represent the DTO classes for the frontend user:
public class RecipeModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public IEnumerable<RecipePartModel> RecipeParts { get; set; }
}
public class IngredientModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class RecipePartModel
{
public Guid Id { get; set; }
public IngredientModel Ingredient { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
Here are my entity classes:
public class Recipe : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class Ingredient : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class RecipePart : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public Ingredient Ingredient { get; set; }
public Recipe Recipe { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
My question is - how can I map the Recipe to RecipeModel using AutoMapper? I tried something like this but I assume it is bad, because it just join all the RecipeParts for the whole database, am I correct?
public class DomainProfile : Profile
{
public DomainProfile()
{
CreateMap<Ingredient, IngredientModel>().ReverseMap();
CreateMap<Recipe, RecipeModel>()
.ForMember(x => x.RecipeParts, opt => opt.MapFrom(src => src.RecipeParts));
}
}
To answer your question about how to use AutoMapper to map a type to another type, there are many ways of doing this. Documentation is here: http://docs.automapper.org/en/stable/Getting-started.html.
I wrote a console app and got it working in the quickest way I know possible using your code. When I debug this, and check inside recipeModel, it references a list of RecipePartModels with a single RecipePartModel. Inside that RecipePartModel, it references an IngredientModel.
static void Main(string[] args)
{
var profile = new DomainProfile();
Mapper.Initialize(cfg => cfg.AddProfile(profile));
var recipe = new Recipe
{
RecipeParts = new List<RecipePart>
{
new RecipePart()
{
Ingredient = new Ingredient()
}
}
};
var recipeModel = Mapper.Map<Recipe, RecipeModel>(recipe);
Console.ReadKey();
}
To answer your concern about getting all recipes from the database, if you're using Entity Framework, it depends on if you have lazy loading turned on. Lazy loading ensures that, when you get a recipe from the database, the recipe parts will not be loaded. They will only be loaded when you access the recipe part directly later on in the program flow. Lazy loading is turned on by default so this is the default behaviour. If you turn it off, you've enabled eager loading which loads all recipe parts and in turn their ingredient.
This might help: http://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx.
There is nothing bad about this mapping. In fact you don't even need the ForMember call as this is the default convention. The mapping will simply convert each element in the entity child collection to a corresponding model object.
Of course, whether you load your entities in an efficient manner is another matter. If you load a large amount of Recipe entities, and lazy load the RecipeParts collections for each, you will have a major "SELECT N+1" problem. But this is not the fault of AutoMapper.

2 tables in Local DB in MVC using a foreign key to interact - Error, returns "The property cannot be configured as a navigation property

So my teammates and I are building a website that aggregates textbook prices from different textbook websites (we aren't dynamically getting the prices from the websites themselves, but for the purposes of the school project we are in, we are just randomly entering them in).
So I've got two tables in my database Book1 - a "Books" table and a "Prices" table. I have a foreign key in my Prices table (screenshot of design view) that relates back to my Books table (screenshot of design view).
The ISBN field of our Prices table is a foreign key to the ISBN field of our Books table.
We believe we've set everything else up correctly but when we run it, it returns an exception saying:
The property 'ISBN' cannot be configured as a navigation property. The
property must be a valid entity type and the property should have a
non-abstract getter and setter. For collection properties the type
must implement ICollection where T is a valid entity type.
Here are all the relevant classes.
Book.cs
public partial class Book
{
[Key, Required]
[StringLength(14)]
public string ISBN { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
public string YearPublished { get; set; }
public virtual Price Price { get; set; }
}
Price.cs
public partial class Price
{
[Key, Required]
public int PriceID { get; set; }
[ForeignKey("ISBN")]
public string ISBN { get; set; }
public decimal? AmazonPrice { get; set; }
public decimal? BarnesAndNoblePrice { get; set; }
public decimal? CheggPrice { get; set; }
public decimal? SecondAndCharlesPrice { get; set; }
public decimal? AlibrisPrice { get; set; }
public decimal? ThriftBooksPrice { get; set; }
public decimal? ValoreBooksPrice { get; set; }
public virtual Book Book { get; set; }
public virtual IEnumerable<Book> Books { get; set; }
}
MyModel.cs
public class MyModel : DbContext
{
public MyModel()
: base("name=DefaultConnection") { }
public virtual DbSet<Book> Books { get; set; }
public virtual DbSet<Price> Prices { get; set; }
}
HomeController.cs
public ActionResult Index()
{
var model = new IndexVM();
var ctx = new MyModel();
foreach (var bk in ctx.Books)
{
model.Books.Add(bk);
}
return View(model);
}
We've been told by our professor that it has something to do with the IEnumerable class, but we've tried every combination in the book and we still can't get it to work.
Any and all help will be appreciated.
In Price.cs
[ForeignKey("ISBN")]
public string ISBN { get; set; }
should read
[ForeignKey("Book")]
public string ISBN { get; set; }
Additionally in Book.cs I think you need somewhere to store the cross reference to Price
[ForeignKey("Price")]
public int PriceId { get; set; }
this question describes the same issue Foreign Key Annotation in MVC

Entity Framework: map multiple similar classes to one table in similar databases

I have model with table in databases of my clients:
public class Doctor
{
public int Id { get; set; }
public int Filial { get; set; }
public string ShortName { get; set; }
public string FullName { get; set; }
public string Phone { get; set; }
public int? DepartmentId { get; set; }
public Department Department { get; set; }
}
public class DoctorConfiguration : EntityTypeConfiguration<Doctor>
{
public DoctorConfiguration()
{
ToTable("DOCTOR");
Property(d => d.Id).HasColumnName("DCODE").IsRequired();
Property(d => d.Filial).HasColumnName("FILIAL");
Property(d => d.ShortName).HasColumnName("DNAME");
Property(d => d.FullName).HasColumnName("FULLNAME");
Property(d => d.Phone).HasColumnName("DPHONE");
Property(d => d.DepartmentId).HasColumnName("DEPNUM");
HasKey(d => d.Id);
HasOptional(d => d.Department).WithMany(dep => dep.Doctors).HasForeignKey(d => d.DepartmentId);
}
}
Recently additional clients came. Their databases has mostly the same models, but some fields had changed types from int to long.
The new Doctor model looks like:
public class Doctor
{
public long Id { get; set; }
public long Filial { get; set; }
public string ShortName { get; set; }
public string FullName { get; set; }
public string Phone { get; set; }
public long? DepartmentId { get; set; }
public Department Department { get; set; }
}
How to properly map new Doctor model format to the same table "DOCTOR"?
The application uses Firebird database. Version for "old" clients doesn't support long numbers format.
In case of creating similar Doctor configuration, an error appears:
"The entity types 'DoctorInt' and 'Doctor' cannot share table 'DOCTOR' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them."
I know about Table-per-Hierarchy (TPH) Inheritance. Looks like it can't help in this situation.
The type Doctor is only the one of the many similar types with this problem. The code in an application is connected with first models format. I wouldn't like to change it all...
I would like to reuse existing functionality.
If I don't misunderstand, you need to support old and new databases with the same code and databases only differs on the size of IDs
An approach is to use generics and conditional compilation
public class Doctor<T> {
public T Id { get; set; }
public int Filial { get; set; } //Suposing Filial is not a foreing key
public string ShortName { get; set; }
public string FullName { get; set; }
public string Phone { get; set; }
public T? DepartmentId { get; set; }
public Department Department { get; set; }
}
When istantiating:
#ifdef USELONG
var d = new Doctor<long>();
#else
var d = new Doctor<int>();
#endif
Or with a factory pattern (where CreateDoctor may be a static method of Doctor class):
var d = Doctor.CreateDoctor();

How to model with EntityFramework extensible fields for the entities

I have the following requirement, on my app the Entities will come with some fields, however the user needs to be able to add additional fields to the entity and then values for those fields.
I was thinking something like this but I am not sure if it would be a good approach or not.
The base class is an entity (Not sure which fields I need to add here)
public class Entidad
{
}
Then the Company Class will inherit from Entity
public class Empresa : Entidad
{
[Key]
public int Id { get; set; }
public string Nombre { get; set; }
public string NIT { get; set; }
public string NombreRepresentanteLegal { get; set; }
public string TelefonoRepresentanteLegal { get; set; }
public string NombreContacto { get; set; }
public string TelefonoContacto { get; set; }
public ICollection<CampoAdicional> CamposAdicionales { get; set; }
}
As you can see there is an ICollection of additional fields. that class would have the fieldname, type and id
public class CampoAdicional
{
[Key]
public int Id { get; set; }
public string NombreCampo { get; set; }
public Tiposcampo TipoCampo { get; set; }
}
and then the field value would be something like this:
public class ValorCampo
{
public Entidad Entidad { get; set; }
public CampoAdicional Campo { get; set; }
public string ValorTexto { get;set ; }
public int ValorNumerico { get; set; }
}
However I am not sure if this is the correct model classes for my scenario and whether it would create the tables correctly.
EF works with lazy load so at least there are several "virtual" missings.
In all properties that does not use primitive types and in collections.
Can you extend more than one entity with additional fields? If so you need that ValorCampo contains the entity (Entidad) but the entity should have the Id so you need to move the Id from Empresa to Entidad. Otherwise you need ValorCampo should refer to Empresa not to Entidad

Entity Framework 6.1 - code first - reference properties not loading correctly

I've noticed an issue with EF 6.1 code first. I have the following classes -
namespace Domain
{
public interface ISupportsOptimisticConcurrency
{
byte[] RowVersion { get; set; }
}
public class Entity : ISupportsOptimisticConcurrency
{
public int Id { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
public class Lookup : Entity
{
public Lookup()
{
Description = string.Empty;
}
[Required]
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(300)]
public string Description { get; set; }
}
public class GroupType : Lookup
{
}
public class Group:Entity
{
public Group()
{
GroupType = new GroupType();
}
[Required]
public string Name { get; set; }
[Required]
public Guid ExternalId { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string MonitorEmail { get; set; }
public string UrlRequestEmail { get; set; }
public bool UsesDefaultOptions { get; set; }
[ForeignKey("GroupType")]
public int GroupTypeId { get; set; }
public virtual GroupType GroupType { get; set; }
}
}
I've written a typical Repository class for accessing data from DB. Now, when I try to find a Group by Id, and include the GroupType, the GroupType doesn't load properly, and the Name property of GroupType comes as null.
Interestingly, when I removed the Group constructor which initializes a new GroupType, things start working fine.
Could you please explain this behavior?
Note: This same scenario works fine with NHibernate as it is.
Thanks for the replies.
I think you have to remove the initialization logic in the Group constructor:
GroupType = new GroupType();
This probably overwrites the loaded data or does not even load it (because it already was instantiated), causing the GroupType property to be the instance that you initialized it with instead of the one in the database.
It may be the same issue as explained here.

Categories

Resources