Automapper mapping to properties from a nested object [duplicate] - asp.net-core

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

Related

EF Core - Migrate from Table Per Type to Table Per Hierarchy

I'm trying to migrate from using TPT (a table per subclass) to TPH (One table for all subclasses).
This is my starting point for TPT:
entities:
[Serializable]
public abstract class VeganItem<TVeganItemEstablishment> : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
[PropertyName("veganItemEstablishments", Ignore = true)]
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
[ElasticsearchType(RelationName = "groceryitem", IdProperty = "Id")]
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
[ElasticsearchType(RelationName = "menuitem", IdProperty = "Id")]
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
OnModelCreating:
modelBuilder.Entity<GroceryItem>(gi =>
{
gi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
gi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.HasKey(e => e.Id);
gi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
gi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
gi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
modelBuilder.Entity<MenuItem>(mi =>
{
mi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
mi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.HasKey(e => e.Id);
mi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
mi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
mi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
public DbSet<GroceryItem> GroceryItems { get; set; }
public DbSet<MenuItem> MenuItems { get; set; }
So I want just one table called VeganItems. What is it that is actually causing there to be 2 tables - GroceryItems and MenuItems? As I have tried a couple of things and they didn't work. EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
Generic type is one of the problems. The other is that it is not a base entity, but just base class. In order to be considered entity, there must be either DbSet<T>, or ModelBuilder.Entity<T>() call, or applied IEntityTypeConfiguration<T>, or some discorevered entity navigation property (either collection or reference) referring to it - see Including types in the model.
You don't have any of these, so the model is not even TPT (common table containing common properties + single table per each derived entity containing specific properties), but some sort of a TPC (Table-Per-Class, not currently supported by EF Core), where there is no common table - all the data is in concrete tables for each derived entity.
So, in order to use TPT you need to fix both issues. Generic class cannot be used as entity type because its type is not enough to identify it (each generic instantiation is different type, typeof(Foo<Bar>) != typeof(Foo<Baz>)).
Start by extracting the non generic part which will serve as base entity (removed non EF Core annotations for clarity):
// Base class (code/data reuse only, not an entity)
public abstract class DomainEntity<TId>
{
public TId Id { get; set; }
}
// Base entity
public abstract class VeganItem : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
}
// Base class (code/data reuse only, not an entity)
public abstract class VeganItem<TVeganItemEstablishment> : VeganItem
{
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
// Derived entity
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
// Derived entity
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
Then (optionally) add DbSet for it
public DbSet<VeganItem> VeganItems { get; set; }
Finally (mandatory) move the fluent configuration of the base entity members to its own block, and keep in derived only the configuration of the specific members of the derive type:
// Configure base entity
modelBuilder.Entity<VeganItem>(vi =>
{
vi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
vi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.HasKey(e => e.Id);
vi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
vi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
vi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
// Configure derived entities
modelBuilder.Entity<GroceryItem>(gi =>
{
});
modelBuilder.Entity<MenuItem>(mi =>
{
});

How map depending list to counting property with Automapper

I Have this Entity:
public class Skill : Base
{
public Skill() { }
public string Name { get; set; }
public string Description { get; set; }
public List<UserSkill> UserSkills { get; set; }
}
And this DTO:
public class SkillDto
{
public SkillDto() { }
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int AssignedUsers { get; set; } = 0;
}
This is my Automapper configuration:
configuration.CreateMap<Skill, SkillDto>()
.ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name))
.ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.Description))
.ForMember(dest => dest.AssignedUsers, opts => opts.MapFrom(src => src.UserSkills.Count));
The last line, tries to count List<> to int property
.ForMember(dest => dest.AssignedUsers, opts => opts.MapFrom(src => src.UserSkills.Count));
But, always catch this exception:
Unmapped members were found. Review the types and members below. Add a
custom mapping expression, ignore, add a custom resolver, or modify
the source/destination type For no matching constructor, add a no-arg
ctor, add optional arguments, or map all of the constructor parameters
================================================================= Skill -> SkillDto (Destination member list)
Uranus.Domain.Entities.Skill -> Uranus.WebAPI.Models.Skill.SkillDto
(Destination member list)
Unmapped properties: AssignedUsers
What's wrong?

AutoMapper error: class maps to a datatype (byte[])

I have two classes, each having a domain and a repo version.
DOMAIN:
public class MusicInfo
{
public string Id { get; set; }
public MusicImage Image { get; set; }
public MusicInfo(byte[] image)
{
this.Image = new MusicImage(this, image);
}
}
public class MusicImage
{
public byte[] Blob { get; set; }
public MusicInfo MusicInfo { get; set; }
public string Id { get; set; }
public MusicImage(MusicInfo musicInfo, byte[] blob)
{
if (musicInfo == null)
throw new ArgumentNullException("musicInfo");
if (blob == null)
throw new ArgumentNullException("blob");
this.MusicInfo = musiscInfo;
this.Blob = blob;
}
}
REPO:
public class MusicInfoRepo
{
public virtual long Id { get; set; }
public virtual MusicImageRepo Image { get; set; }
}
public class MusicImageRepo
{
public virtual byte[] Blob { get; set; }
public virtual MusicInfoRepo MusicInfo { get; set; }
public virtual long Id { get; set; }
}
And here are their mappings:
public class MusicInfoRepoMap : HighLowClassMapping<MusicInfoRepo>
{
public MusicInfoRepoMap()
{
Table("MusicInfo");
Id(f => f.Id, m => m.Generator(Generators.HighLow, HighLowMapper));
OneToOne(f => f.Image, m => m.Cascade(Cascade.All));
}
}
public class MusicImageRepoMap : ClassMapping<MusicImageRepo>
{
public MusicImageRepoMap()
{
Table("MusicImage");
Id(f => f.Id, m => m.Generator(Generators.Foreign<MusicImageRepo>(f => f.MusicInfo)));
Property(f => f.Blob, m =>
{
m.NotNullable(true);
m.Column(c => c.SqlType("VARBINARY(MAX)"));
m.Length(Int32.MaxValue);
m.Update(false);
});
OneToOne(f => f.MusicInfo,
m =>
{
m.Cascade(Cascade.None);
m.Constrained(true);
m.Lazy(LazyRelation.NoLazy);
});
}
}
When I am trying to query for a ClassA that has a one to one relationship with ClassB which also has a one to one relationship with MusicInfo, an error occurs that says:
Missing type map configuration or unsupported mapping.
Mapping types:
MusicImageRepo -> Byte[]
Blah.MusicImageRepo -> System.Byte[]
Destination path:
List`1[0]
Source value:
Blah.MusicImageRepo
but here is how i map them:
Mapper.CreateMap<MusicInfo, MusicInfoRepo>();
Mapper.CreateMap<MusicInfoRepo, MusicInfo>();
Mapper.CreateMap<MusicImage, MusicImageRepo>();
Mapper.CreateMap<MusicImageRepo, MusicImage>();
There are no problems when saving these classes.
I really dont get why the error happens.
Will really appreciate your help.
OneToOne says that the other entity/table has the Reference(column). It should not work when both sides have a onetoone mapping since they bounce the responsiblity back and forth.
Also the classes look a bit overcomplicated when all you need is to store the bytes somewhere else.
public class MusicInfoRepo
{
public virtual long Id { get; private set; }
public virtual MusicImageRepo Image { get; private set; }
public MusicInfo(byte[] image)
{
this.Image = new MusicImageRepo(this, image);
}
}
public class MusicImageRepo
{
public virtual MusicInfoRepo MusicInfo { get; private set; }
public virtual byte[] Blob { get; set; }
}
public class MusicInfoRepoMap : HighLowClassMapping<MusicInfoRepo>
{
public MusicInfoRepoMap()
{
Table("MusicInfo");
Id(f => f.Id, m => m.Generator(Generators.HighLow, HighLowMapper));
OneToOne(f => f.Image, m => m.Cascade(Cascade.All));
}
}
public class MusicImageRepoMap : ClassMapping<MusicImageRepo>
{
public MusicImageRepoMap()
{
Table("MusicImage");
ComposedId(m => m.ManyToOne(x => x.MusicInfo));
Property(f => f.Blob, m =>
{
m.NotNullable(true);
m.Column(c => c.SqlType("VARBINARY(MAX)"));
m.Length(Int32.MaxValue);
m.Update(false);
});
}
}
Note: think about it if the seperation between DomainModel and MappedModel really makes sense. NHibernate goes to great length supporting mapping of DomainModels.

How to order a HasMany collection by a child property with Fluent NHibernate mapping

I am using Fluent NHibernate to map the following classes:
public abstract class DomainObject
{
public virtual int Id { get; protected internal set; }
}
public class Attribute
{
public virtual string Name { get; set; }
}
public class AttributeRule
{
public virtual Attribute Attribute { get; set; }
public virtual Station Station { get; set; }
public virtual RuleTypeId RuleTypeId { get; set; }
}
public class Station : DomainObject
{
public virtual IList<AttributeRule> AttributeRules { get; set; }
public Station()
{
AttributeRules = new List<AttributeRule>();
}
}
My Fluent NHibernate mappings look like this:
public class AttributeMap : ClassMap<Attribute>
{
public AttributeMap()
{
Id(o => o.Id);
Map(o => o.Name);
}
}
public class AttributeRuleMap : ClassMap<AttributeRule>
{
public AttributeRuleMap()
{
Id(o => o.Id);
Map(o => o.RuleTypeId);
References(o => o.Attribute).Fetch.Join();
References(o => o.Station);
}
}
public class StationMap : ClassMap<Station>
{
public StationMap()
{
Id(o => o.Id);
HasMany(o => o.AttributeRules).Inverse();
}
}
I would like to order the AttributeRules list on Station by the Attribute.Name property, but doing the following does not work:
HasMany(o => o.AttributeRules).Inverse().OrderBy("Attribute.Name");
I have not found a way to do this yet in the mappings. I could create a IQuery or ICriteria to do this for me, but ideally I would just like to have the AttributeRules list sorted when I ask for it.
Any advice on how to do this mapping?
I think the OrderBy-method takes in the string that it inserts to the generated SQL-clause. So just doing
HasMany(o => o.AttributeRules).Inverse().OrderBy("Name");
Where the "Name" is the name of the column that contains Attribute's name. It should be in the column list because Attribute is joined to the AttributeRule.
Did you solve this other way? Please share.

entity framework 4, code-only, relationships

I can't figure out how to solve the following problem.
What i need it a relationship from one base class to another, so that every derived class has a relationship with the same table, called 'Item' in my example.
Since this is just an example it doesn't reflect my program. In the real program the relationship with class Item is in a different namespace. Therefore it can't be in the derived class.
The error:
A key is registered for the derived type 'WebApplication1.Client'. Keys must be registered for the root type 'WebApplication1.Base'.
namespace WebApplication1
{
public class Item
{
public int ItemID { get; set; }
}
public class Base
{
public int ID { get; set; }
public int ItemID { get; set; }
public Item Item { get; set; }
}
public class Client : Base
{
public string Name { get; set; }
private List<Project> _projects = null;
public List<Project> Projects
{
get
{
if (_projects == null)
_projects = new List<Project>();
return _projects;
}
}
}
public class Project : Base
{
public string Name { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
}
public class Main
{
public static void Test()
{
ContextBuilder<ObjectContext> ContextBuilder = new ContextBuilder<ObjectContext>();
var itemConfig = new EntityConfiguration<Item>();
itemConfig.HasKey(p => p.ItemID);
itemConfig.Property(p => p.ItemID).IsIdentity();
ContextBuilder.Configurations.Add(itemConfig);
var clientConfig = new EntityConfiguration<Client>();
clientConfig.HasKey(p => p.ID);
clientConfig.Property(p => p.ID).IsIdentity();
clientConfig.Property(p => p.Name);
clientConfig.Relationship(p => p.Item).HasConstraint((p, c) => p.ItemID == c.ItemID);
ContextBuilder.Configurations.Add(clientConfig);
var projectConfig = new EntityConfiguration<Project>();
projectConfig.HasKey(p => p.ID);
projectConfig.Property(p => p.ID).IsIdentity();
projectConfig.Property(p => p.Name);
projectConfig.Relationship(p => p.Item).HasConstraint((p, c) => p.ItemID == c.ItemID);
projectConfig.Relationship(p => p.Client).FromProperty(p => p.Projects).HasConstraint((p, c) => p.ClientId == c.ID);
ObjectContext objCtx = ContextBuilder.Create(new SqlConnection(#"Data Source=(local);Initial Catalog=testa;Integrated Security=SSPI;"));
if (!objCtx.DatabaseExists())
objCtx.CreateDatabase();
}
}
}
Look at how do deal with inheritance mapping here: Link
For basic non-relational proberties and how-to reuse them: https://danielwertheim.wordpress.com/2009/11/29/entity-framework-4-how-to-reuse-mappings-and-add-a-concurrency-token/