Automapper Ignore nested property - c#

I've search the internet for a few hours now and can't seem to figure out any solutions for myself, or understand some of other similar answers I'm finding.
All I'm trying to do is ignore property from a nested object in my AutoMapper. Here's a small overview of the Models I'm working with (I removed some properties to make them a bit smaller for the purposes of this question).
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class ProductDto
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
public DateTime Created { get; set; }
}
public class CategoryDto
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
}
Basically all I want is my automapper to ignore the Created property coming from the Category class anytime a Product is queried via API. The closest I've to achieving this is by having the whole Category object ignored when queried.
Here is the current mapping configuration for my Product class
public class ProductMapping: Profile
{
public ProductMapping()
{
CreateMap<Product, ProductDto>()
.ReverseMap()
.ForMember(x => x.ProductId, o => o.Ignore());
}
}
I was able to null out the whole object by putting .ForPath(x => x.Category.Created, o => o.Ignore() before .ReverseMap()
I should note that of course the classes and mapper class are distributed through multiple files and the CategoryMapping class looks the same as the ProductMapping. It is removing the Created property, though that is expected.
If anyone can help isolate my issue, or demonstrate a better way to achieve this I am open to suggestions. Till then I will continue trying to figure out this issue. Thanks for any help!

If I understand correctly if you want to ignore the Created field from Category class then you should maybe put the ignore logic when mapping from CategoryDto -> Category or vice versa and mapping from ProductDto <-> remains the same.
CreateMap<Product, ProductDto>()
.ReverseMap()
CreateMap<Category, CategoryDto>()
.ReverseMap()
.ForMember(x => x.Created, o => o.Ignore());

May have answered my own question, but what I had to do was switch the Data type in my ProductDto from Category to CategoryDto. I was under the assumption that Automapper would sort of take care of that itself.
Sorry for that! Thank you to person who took the time to give me an answer aswell!

Related

Is it possible to use an owned type for many-to-many relations?

In Entity Framework Core version 2.2 or 3.0, is it possible to use owned/complex types in such a way that this kind of configuration is possible:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public ProductProperties Properties { get; set; }
}
public class ProductProperties {
public List<ProductSize> Sizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize {
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Product)
.WithMany(x => x.Properties.Sizes)
.HasForeignKey(x => x.ProductId);
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Size)
.WithMany()
.HasForeignKey(x => x.SizeId);
The error message which is seen for this kind of approach usually ends up in:
'x => x.Properties.Sizes' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'
An earlier found answer is almost exactly matching my question, but this was posted in 2013. By the time it was almost certainly not possible.
HasForeignKey relationship through a Complex Type property
The sources on Microsoft are only giving examples for creating an entity with the complex type itself, not for creating relationships between them.
The cause of the issue
In your sample code it's quite clear there is no specific Many to Many relation. To make my argument a bit more convincing what follows is a model of your entities and their relations:
The new class structure
For a Many to Many relation to work in EF the product and size tables need to have an implicit relation with each other through a singular junction table. In my proposed solution I've chosen the ProductProperty table. There I've added the fields from the productsize junction table:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
public class ProductProperty
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
The functions
modelBuilder.Entity<ProductProperty>()
.HasKey(pp => new { pp.ProductId, pp.SizeId });
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Product)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.ProductId);
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Size)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.SizeId);
Additional advice (EDIT)
Make the "Size" class a generic property class. This way the Many-to-Many relation won't get broken and querying will also be very easy:
public class Property
{
public int Id { get; set; }
public PropertyType propType { get; set; }
public string propValue { get; set; }
}
public enum PropertyType
{
Size,
Fontsize,
...
}
As a final argument this change will make it easier to change existing properties or add new ones
Sources
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
You can check the owned entity types released in 2019 Check documentation here
An example from the link is the following:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
The owns many function should help you like this:
modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Let me know if I misunderstood your question.

Entity Framework: Get multiple linked Objects

I did research on my problem, but somehow I don't get it.
I have 4 classes with linked entities in a 1:n way:
public class ContentProtectionProject
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlToProtect> UrlsToProtect { get; set; }
[Required]
public int AccountId { get; set; }
[ForeignKey("AccountId")]
public Account Account { get; set; }
}
public class UrlToProtect
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlTextContent> UrlsTextContent { get; set; }
[Required]
public int ContentProtectionProjectId { get; set; }
[ForeignKey("ContentProtectionProjectId")]
public ContentProtectionProject ContentProtectionProject { get; set; }
}
public class UrlTextContent
{
[Required]
public int Id { get; set; }
...
[Required]
public List<UrlTextSnippet> UrlTextSnippets { get; set; }
[Required]
public int UrlToProtectId { get; set; }
[ForeignKey("UrlToProtectId")]
public UrlToProtect UrlToProtect { get; set; }
}
public class UrlTextSnippet
{
[Required]
public int Id { get; set; }
...
[Required]
public int UrlTextContentId { get; set; }
[ForeignKey("UrlTextContentId")]
public UrlTextContent UrlTextContent { get; set; }
}
I like to get all data for a project, which I try to get this way by projectId from a repository:
public async Task<ContentProtectionProject> GetContentProtectionProject(int contentprotectionProjectId)
{
var contentProtectionProject = await _context.ContentProtectionProjects
.Include(x => x.UrlsToProtect)
.ThenInclude(u => u.UrlsTextContent)
.FirstOrDefaultAsync(x => x.Id == contentprotectionProjectId);
return contentProtectionProject;
}
I am only able to go to the level of "UrlTextContent", but I am somehow not able to include "UrlTextSnippet".
My goal is to load a complete "Project" to be able to do some processing on the linked entities list items.
In the end I want to find all "UrlTextContent" for which no "UrlTextSnippets" are available by iteration over the linked entities.
I use .NET Core 2.1.403 with Entity Framework Core .NET 2.1.4-rtm-31024
Any help is very much appreciated.
Best regards
Edit:
Context class:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base (options) {}
...
public DbSet<ContentProtectionProject> ContentProtectionProjects { get; set; }
public DbSet<UrlToProtect> UrlToProtects { get; set; }
public DbSet<UrlTextContent> UrlTextContents { get; set; }
public DbSet<UrlTextSnippet> UrlTextSnippets { get; set; }
}
Edit 2: Debug screenshot
"UrlTextSnippet" list is null, although one entry is available.
I hate those people that say you can do something you aren't able to do, so sorry in advance. According to the Documentation you should be able to chain those .ThenInclude() or the .Include(). Hopefully these get you on the right track.
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.Include(blog => blog.Owner)
.ThenInclude(owner => owner.Photo)
.ToList();
}
There is also this but I try to avoid selects if I can.
Additionally you might be running into this issue and you might be able to do it but the IntelliSense may be leading you astray.
Note: Current versions of Visual Studio offer incorrect code completion
options and can cause correct expressions to be flagged with syntax
errors when using the ThenInclude method after a collection navigation
property. This is a symptom of an IntelliSense bug tracked at
https://github.com/dotnet/roslyn/issues/8237. It is safe to ignore
these spurious syntax errors as long as the code is correct and can be
compiled successfully.

Automapper - Nested Lists

I am working with AutoMapper, which I am relatively new with, and I stumbled upon a small mapping problem I was hoping the community could assist with.
So I have two data transfer objects:
public class UserDto {
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<CharacterDto> Characters { get; set; }
}
public class CharaterDto {
public string CharacterName { get; set; }
public string ClassName { get; set; }
public int CharacterLevel { get; set; }
}
and two Domain Entities
public class Character {
public int ID { get; set; }
public int UserId { get; set; }
public string CharacterName { get; set; }
public string ClassName { get; set; }
public int CharacterLevel { get; set; }
}
public class User {
public int ID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
The end goal is to be able to save the data taken in by the DTOs into the database via the Domain Entities; however, when it comes to typing up the list of Characters for 'UserDto', I do not know how to map this properly with AutoMapper. I can map it manually with little to no problems... but I can't find anything that helps to explain this or any examples that would help me understand it better.
I have tried doing things like:
CreateMap<UserDto, Character>()
.ForMember(dest => dest.CharacterName, m => m.MapFrom(source => source.Characters[0].CharacterName));
However, this seems to only map the 1st entry and not the others. I have also considered mapping the individual mappings like so:
CreateMap<CharacterDto, Character>();
CreateMap<UserDto, Character>()
.ForMember(?/*this section I cannot figure out*/)
But can't figure out how to associate the the collection of characters to the mapped CharacterDto. I doubt that if I run the code without that association, the code is going to automatically understand that for each character in characters, map each character using the appropriate mapper... If I must manually do this, I can... but if there is an AutoMapper way, any help constructing it would be greatly appreciated.
Type converters are you friend here for mapping 1 to many like this.
Let me know if you need me to go further and get you a working example from your models.
https://stackoverflow.com/a/18096914/7911333

Entity Framework - Invalid column name 'CourseLesson_Id'

After trying to execute the following query:
List<CourseLesson> courseLessons = (from cl in context.CourseLessons
.Include(x => x.CourseLessonTestQuestions)
select cl).ToList();
I get the the error Invalid column name 'CourseLesson_Id'.
My models and DataContext looks like this(this is from a test project I've created to repreduce the problem)
public class CourseLesson
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<CourseLessonTestQuestion> CourseLessonTestQuestions { get; set; }
}
public class CourseLessonTestQuestion
{
public int Id { get; set; }
public int CourseLessonId { get; set; }
[ForeignKey(nameof(CourseLessonId))]
public CourseLesson CourseLesson { get; set; }
public int? ReturnCourseLessonId { get; set; }
[ForeignKey(nameof(ReturnCourseLessonId))]
public CourseLesson ReturnCourseLesson { get; set; }
}
I have 2 foreign keys that point to the same table and I'm assuming EF is trying to create or map something that doesn't really exist.
After reading for a while I've found a way to fix my problem in (this answer) with the following code:
modelBuilder.Entity<CourseLessonTestQuestion>()
.HasOptional(cltq => cltq.ReturnCourseLesson)
.WithMany(x => x.CourseLessonTestQuestions);
What really bugs me about this situation is why everything works when I use the Fluent API, but it doesn't work with the ForeignKey attribute? This looks like something that could lead to future problems and I want to know what is really happening.
And the real question is there a solution for fixing this problem without the Fluent API? Like using attributes or some other convention?
I'm using Entity Framework 6.1.3
Solution without Fluent API, but with the help of InversePropertyAttribute, whose constructor's argument is the name of corresponding CourseLessonTestQuestion's property:
public class CourseLesson
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[InverseProperty("CourseLesson")]
public ICollection<CourseLessonTestQuestion> CourseLessonTestQuestions { get; set; }
[InverseProperty("ReturnCourseLesson")]
public ICollection<CourseLessonTestQuestion> ReturnCourseLessons { get; set; }
}

How do you map many-to-many relationships?

How does one map many to many relationships?
One-to-one relationships are easy....
Assuming...
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int SupplierId { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Cost { get; set; }
public Supplier Supplier { get; set; }
}
public class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
public int Rating { get; set; }
public ICollection<Product> Products { get; set; }
}
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierId, m => m.MapFrom(s => s.Supplier.Id));
Works assuming every product has only one supplier and a supplier can have many products. How do I map it if a product can also have many suppliers?
I changed supplier line in Products to
public ICollection<Supplier> Supplier { get; set; }
and in ProductDTO doing the same
public ICollection<int> SupplierId { get; set; }
How do I alter CreateMap since the Ids are now collections? Autocomplete no longer shows Id and all I get are functions.
I'm new to C# so I many be missing something obvious. Am I supposed to iterate in a for loop and map the ids one by one?
You may try to use:
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierIds, m => m.MapFrom(p => p.Suppliers.Select(s => s.Id)));
Another option:
Mapper.CreateMap<Supplier, int>().ConvertUsing(s => s.Id);
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierIds, m => m.MapFrom(p => p.Suppliers));
One more thing if you are this using DTO to pass data from Web/WCF service you may consider using
public ICollection<SupplierDTO> Supplier { get; set; }
instead if passing supplier ids only. In most cases it's better (and more effective) to pass more data in one call to the service than doing few calls.

Categories