How map depending list to counting property with Automapper - asp.net-core

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?

Related

Automapper mapping to properties from a nested object [duplicate]

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

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

Nhibernate projection with alias to nested property

I have a DTO class like this:
public class Aggregates
{
public Aggregates()
{
Sales = new StatisticAggregates();
Refund = new StatisticAggregates();
}
public int ChecksAmount { get; set; }
public StatisticAggregates Sales { get; set; }
public StatisticAggregates Refund { get; set; }
public int TotalAmount { get { return Sales.Amount - Refund.Amount; } }
public double TotalPrice { get { return Sales.Price - Refund.Price; } }
}
public class StatisticAggregates
{
public int Amount { get; set; }
public double Cash { get; set; }
public double Cashless { get; set; }
public double Price { get { return Cash + Cashless; } }
}
And I have a projections list like this:
Aggregates alias = null;
new List<IProjection>
{
Projections.RowCount().WithAlias(() => alias.ChecksAmount),
Projections.Sum(() => sale.Total.Amount).WithAlias(() => alias.Sales.Amount),
Projections.Sum(() => sale.Payment.Cash).WithAlias(() => alias.Sales.Cash),
Projections.Sum(() => sale.Payment.Cashless).WithAlias(() => alias.Sales.Cashless),
Projections.Sum(() => sale.Total.RefundAmount).WithAlias(() => alias.Refund.Amount),
Projections.Sum(() => sale.Refund.Cash).WithAlias(() => alias.Sales.Cash),
Projections.Sum(() => sale.Refund.Cashless).WithAlias(() => alias.Sales.Cashless),
};
and transforms query with query.Select(projections.ToArray()).TransformUsing(Transformers.AliasToBean<Aggregates>();
In runtime TransformUsing() throws an Exception:
Could not find a setter for property 'Amount' in class 'Namespace.Domain.Services.Aggregates'
This is quite expected, because of NHibernate select the name of the property from a member expression (without leading member access).
So, how can I setup projections, which will fill in property of property Aggregates class?

Using NHibernate QueryOver with a complex scenario

I´m trying to use QueryOver in that scenario :
public class Class1
{
public virtual string Name { get; set; }
public virtual string Descripton { get; set; }
public virtual Class2 { get; set; }
public virtual IList<Class3> ListClass3{ get; set; }
... //SEVERAL OTHERS LISTS, PROPERTIES
}
public class Class2
{
public virtual string Name { get; set; }
... //SEVERAL OTHERS LISTS, PROPERTIES
}
public class Class3
{
public virtual string Name { get; set; }
public virtual Class4 { get; set; }
... //SEVERAL OTHERS LISTS, PROPERTIES
}
public class Class4
{
public virtual string Prop1 { get; set; }
public virtual string Prop2{ get; set; }
... //SEVERAL OTHERS LISTS, PROPERTIES
}
And my DTO :
public class ClassDTO
{
public string NameClass1 { get; set; }
public string DescriptonClass1 { get; set; }
public string NameClass2 { get; set; }
public virtual IList<Class3> Class3List { get; set; }
}
My problem is how get the IList ... Without that, thats working fine so far:
Class2 class2 = null;
IList<Class3> listClass3 = null;
var queryOver = Session.QueryOver<clsClass1>();
var list = queryOver
.JoinAlias(x => x.Class2, () => class2)
.JoinAlias(x => x.ListClass3, () => listClass3, JoinType.LeftOuterJoin)
.SelectList(list => list
.Select(c2 => c2.Name).WithAlias(() => myDTO.NameClass1)
.Select(c2 => class2.Name).WithAlias(() => myDTO.NameClass2)
//NEED GET LIST<CLASS3>
)
.TransformUsing(Transformers.AliasToBean<ClassDTO>())
.List<ClassDTO>();
Thats working fine, but I need to 'fill' the IList now... And if possible, get just the Prop1 and Prop2 from Class4...
Thanks
something that is close to what you want taking one roundtrip
// get ClassDTOs
Class2 class2 = null;
ClassDTO myDTO = null;
var results = Session.QueryOver<Class1>()
.JoinAlias(x => x.Class2, () => class2)
.SelectList(list => list
.Select(c1 => c1.Id).WithAlias(() => myDTO.IdClass1)
.Select(c1 => c1.Name).WithAlias(() => myDTO.NameClass1)
.Select(c1 => c1.Description).WithAlias(() => myDTO.DescriptionClass1)
.Select(() => class2.Name).WithAlias(() => myDTO.NameClass2)
)
.TransformUsing(Transformers.AliasToBean<ClassDTO>())
.Future<ClassDTO>();
// get Class3DTOs
Class3 class3 = null;
Class3DTO myClass3DTO = null;
var subresults = Session.QueryOver<Class1>()
.JoinAlias(x => x.Class3List , () => class3)
.JoinAlias(() => classe3.Class4 , () => class4)
.SelectList(list => list
.Select(c => c.Id)
.Select(() => class3.Name)
.Select(() => class4.Prop1)
.Select(() => class4.Prop2))
.Future<object[]>()
.ToLookup(array => (int)array[0], array => new myClass3DTO
{
NameClass3 = (string)array[1],
Prop1Class4 = (string)array[2],
Prop2Class4 = (string)array[3],
});
// assigne the lists to the dto
foreach (var result in results)
{
result.ListClass3 = subresults[result.IdClass1].ToList();
}
return results;

NHibernate Criteria Queries - how use in One to One relationship

I have simple 3 POCO classes:
public class User
{
//PK
public virtual int UserId { get; set; }
//ONE to ONE
public virtual Profil Profil{ get; set; }
//ONE to MANY
public virtual IList<PhotoAlbum> Albums { get; set; }
}
public class Profil
{
//PK
public virtual int ProfilId { get; set; }
public virtual int Age { get; set; }
public virtual int Sex { get; set; }
}
public class PhotoAlbum
{
//PK
public virtual int PhotoAlbumId { get; set; }
public virtual string Name { get; set; }
public virtual int NumberOfPhoto { get; set; }
}
I created these mapping classes:
public class UserMap : ClassMap<User>
{
public UserMap()
{
//PK
Id(p => p.UserId)
.GeneratedBy.Identity();
//FK
References(p => p.Profil)
.Column("ProfilId")
.Cascade.All();
//ONE TO MANY
HasMany(p => p.Albums)
.Cascade.All();
Table("Users");
}
}
public class ProfilMap: ClassMap<Profil>
{
public ProfilMap()
{
Id(p => p.ProfilId)
.GeneratedBy.Identity();
Map(p => p.Age)
.Not.Nullable();
Map(p => p.Sex)
Table("Profiles");
}
}
public class PhotoAlbumMap : ClassMap<PhotoAlbum>
{
public PhotoAlbumMap()
{
Id(p => p.PhotoAlbumId)
.GeneratedBy.Identity();
Map(p => p.Name)
.Not.Nullable();
Map(p => p.NumberOfPhoto)
.Not.Nullable();
Table("PhotoAlbums");
}
}
Then I created simple NHibernate repository class with this method:
public IList<T> GetItemsByCriterions(params ICriterion[] criterions)
{
ICriteria criteria = AddCriterions(_session.CreateCriteria(typeof(T)),
criterions);
IList<T> result = criteria.List<T>();
return result ?? new List<T>(0);
}
For test I created repository for some entity, for example User:
_userRepo = new NHibRepository<User>(NHibeHelper.OpenSession());
and I would like have possibility make query in this style:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Gt("Profile.Age",10)
});
this attempt finished with error:
could not resolve property: Profile of: Repository.User
User has property Profile type of Profile and this property has properties ProfileId, Age
and sex.
** #1 EDITED:**
# I tried this:
var users = _userRepo.GetItemsByCriterions(new ICriterion[]
{
Restrictions.Where<User>(u=>u.Profil.Sex==0)
});
finished with error:
could not resolve property: Profil.Sex of: Repository.User
#2 EDITED
I tried use Nathan’s advice:
var result = _userRepo.Session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Sex", 0));
IList<User> users=null;
if (result != null)
users = result.List<User>();
If I tried convert result to List I again get this error: could not resolve property: Profile of: Repository.User
Looking at your example, User has a Profil property not a Profile property.
If it is supposed to be Profil then I would change the Restrictions.Gt(Profile.Age,10) to Restrictions.Gt(Profil.Age,10) otherwise change the name of the property and mapping to match the query.
Edit:
You are trying to query the User Object. you need to include the CreateAlias let nhibernate know that you want to link to a different object.
Try This.
var users = session.CreateCriteria<User>()
.CreateAlias("Profile", "profile", JoinType.InnerJoin)
.Add(Restrictions.Eq("profile.Age", 10));