Automapper flattening not behaving as expected - c#

I'm trying to flatted out a nested object. All of the documentation and stack threads seem to suggest the solution is very simple, but it just isn't working.
Consider the following (simplified) objects:
public class CRouteHold
{
public int HoldId { get; set; }
public virtual Hold? Hold { get; set; }
public bool? fhandstart { get; set; }
public bool? ffootstart { get; set; }
public bool? rhandstart { get; set; }
public bool? rfootstart { get; set; }
}
public class Hold
{
public double? xpos { get; set; }
}
and then a flat Dto I want to map to:
public record CRouteHoldDto(
int HoldId,
bool fhandstart,
bool ffootstart,
bool rhandstart,
bool rfootstart,
double xpos
);
Now, I've tried a bunch of mapping profiles, some work and some don't. The one I want to work and which seems like it should work doesn't. Here's what I've tried.
Simple unflatted setup: If I comment out the xpos on the dto and just map the 'root' layer of the RouteHold object like so:
CreateMap<CRouteHold, CRouteHoldDto>();
This works, and gives a CRouteHoldDto.
Flattening setup using a naming convention - if I change the name of xpos on the dto to HoldXpos (camelcase of ObjectProperty) then automapper figures it out and maps the xpos from the child Hold on CRouteHold to the HoldXpos property. The mapping profile is the same as above. Nothing needs to change.
Flatting without naming convention, using ForMember - I don't want the long HoldXpos name on my Dto. I just want xpos, the same as on the child Hold. To do this, the documentation and a myriad of Stack threads seem to suggest I can use a mapping profile like this:
CreateMap<CRouteHold, CRouteHoldDto>()
.ForMember(rh => rh.xpos, m => m.MapFrom(rh => rh.Hold.xpos));
However, it just doesn't seem to work. It throws an error about needing an empty constructor or optional args.
Am I missing something here?
Edit: From comments I also tried using the IncludeMembers by creating an additional map from Hold to CRouteHoldDto:
CreateMap<CRouteHold, CRouteHoldDto>()
.IncludeMembers(s => s.Hold);
CreateMap<Hold, CRouteHoldDto>(MemberList.None);
Unfortuantely, the result is the same.
SOLUTION
Thanks to #LucianBargaoanu. For those in the future, the exact mapping profile now looks like this:
CreateMap<CRouteHold, CRouteHoldDto>()
.ForCtorParam("xpos", m => m.MapFrom(s => s.Hold.xpos))
.ForCtorParam("otherproperty", m => m.MapFrom(s => s.Hold.otherproperty))
So just keep adding on the ForCtorParam for each property you want to map.

You need ForCtorParam instead of ForMember. A record is immutable, so in fact you're doing constructor mapping.
See https://docs.automapper.org/en/latest/Construction.html.

Related

Multiple discriminators for same type

I got the following types
internal enum IssueType
{
First,
Second,
Special
}
internal class Issue
{
public int Id { get; set; }
public string Title { get; set; }
public IssueType Type { get; set; }
}
internal class SpecialIssue : Issue
{
public string Payload { get; set; }
}
Some issue types map to certain subclasses, like Special would map to SpecialIssue in this case. Others just map to Issue i.e. there are several IssueTypes without special subclasses.
Now I would like to have an Issues table to hold all these issues so I configured Issue.Type as a discriminator value.
builder.HasDiscriminator(x => x.Type)
.HasValue<Issue>(IssueType.First)
.HasValue<Issue>(IssueType.Second)
.HasValue<SpecialIssue>(IssueType.Special);
Unfortunately it seems that I cannot set multiple discriminator values for the same type so at runtime I get errors stating that some discriminators weren't mapped although I did.
What I would like to achieve is to map all subclasses to their respective discriminators and have some kind of "fallback" that maps to Issue (or just map any remaining IssueType to the Issue type manually).
Is it possible to achieve this? I couldn't figure out any way to get EFCore to do this.
I'm currently using EFCore 5.0.10.

C# Automapper Nested Object

Ive searched for this for few days now and cant seem to get anything to work, I am using c# MVC Entity Framework with Automapper and im trying to achieve the below ViewModels (mainly LostDocumentVM) to be mapped from my database, all other properties will be set in controllers.
Here is my ViewModels...
DocumentVM
{
Public Enum.HistoricType HistoricType {get;set;}
Public DocumentChildVM Document { get; set;}
}
DocumentChildVM
{
Public bool ShowHistoricLink {get;set;}
Public IEnumerable<ListDocumentVM> DocumentsToReview {get;set;}
}
ListDocumentVM
{
Public int Id {get;set;}
Public string Name {get; set;}
Public DateTime? ReviewDate {get;set;}
}
I initialise the DocumentVM like this...
DocumentVM documentVM = DataContext.SystemUser.Where(x=>x.SustemUserID==LoggedOnUserID).Project().To<DocumentVM>().SingleOrDefault();
And my mapping is like this...
Mapper.CreateMap<SystemUser,DocumentVM>()
.ForMember(dest=>dest.Document.DocumentsToReview, opt=>opt.MapFrom(src=>src.Documents.Where(x=>x.DocumentType == Enum.DocumentType.Assessment));
Im new to AutoMapper and struggling to get more advanced mappings to work.
Yes, your ForMember member must refer to a member on the destination type, and yours is referring to a member on the child type. Instead, you'll need to create an AfterMap function that fills in this information on that child entity.
It's not difficult, but you have a bit of a strange set up where a child object Document has a property DocumentsToReview from another property on the parent DocumentVM:
documentVM.Document.DocumentsToReview =
src.Documents.Where(doc => doc.DocumentType == Enum.DocumentType.Assessment);
When you have to shuffle data between sibling/nephew members, it gets a little more challenging.
To do this with AfterMap:
Mapper.CreateMap<SystemUser, DocumentVM>()
.AfterMap((src, dest) => dest.Document.DocumentsToReview =
src.Documents.Where(doc => doc.DocumentType == Enum.DocumentType.Assessment));

How do you use BsonClassMap to map a POCO domain object property as a manual or DBRef reference?

Using BsonClassMap, is it possible to map a domain object reference while keeping the domain object assembly persistent ignorant (changing the public A Reference { get; set; } property to public MongoDBRef Reference{ get; set; } in the sample class B below is not acceptable).
For this case, the referenced object is not a part of the same aggregate, and should not be stored as a nested document.
Is it possible map two domain objects in a relationship like this:
public class A
{
public Guid Id {get; private set; }
}
public class B
{
public Guid Id { get; private set; }
public A Reference { get; set; }
}
Into the following document structure:
// Collection for class A
{ _id: "11111111-1111-1111-1111-111111111111" }
// Collection class B
{
_id: "22222222-2222-2222-2222-222222222222",
reference_id: "11111111-1111-1111-1111-111111111111"
}
The mapping may look like:
BsonClassMap.RegisterClassMap<A>(cm =>
{
cm.MapIdProperty(c => c.Id)
.SetIdGenerator(new GuidGenerator())
.SetRepresentation(BsonType.String);
}
BsonClassMap.RegisterClassMap<B>(cm =>
{
cm.MapIdProperty(c => c.Id)
.SetIdGenerator(new GuidGenerator())
.SetRepresentation(BsonType.String);
// How do I map the B.Reference to a manual reference (like
// the sample doc structure above) or possibly as a DBRef?
}
So, without changing the model, how do I map the Reference property to object A, from object B as either a DBRef or as a manual references (as in my sample document structure above)?
Is this possible using BsonClassMap? Or in order to use BsonClassMap and keep my domain assembly persistent ignorant, do I need to change the model to something like:
public class A
{
public Guid Id {get; private set; }
}
public class B
{
public Guid Id { get; private set; }
public Guid ReferenceId { get; set; } // Don't reference the object directly,
// just store the Guid to the
// referenced object.
}
I posed this same question to the mongodb-csharp user group and got a response from craiggwilson:
You'll need to change your ReferenceProperty to ReferencePropertyId. We do not support lazy-loading (or eager-loading) of referenced documents.
Since A is not the aggregate for B, then this actually makes more sense when discussing in these terms. Generally, it is unnecessary for a referenced aggregate (B) to be loaded in order to process the referencing aggregate (A). It might be that you do indeed need some information from B. In this case, think about denormalizing a little and creating a true entity (BSummary) whose aggregate is A. This would make sense if some of the summary information is immutable or changes infrequently.

EF code first iterates over all properties while creating object

I use EF Code First in my application and have the following class:
[Table("TBL_XYZ")]
public class XYZ
{
[Required]
public string PropA { get; set; }
[Required]
public int PropB { get; set; }
public int FormulaA
{
get
{
return PropB * Math.PI / 100;
}
}
}
This is how I get the data from the database:
var data = (from e in db.XYZ where e.PropB < 100 select e).ToList();
After I added some more fields which do calculations and don't have a set accessor (like FormulaA), I realized a drop in performance when executing the above line.
After some debugging I found out that EF iterates over all Properties. It calls all get-functions of the properties, while creating the object, even if I don't access them.
What is the purpose of this behaviour and is there a workaround. Does this maybe have something to do with keeping track of changes?
It is really convenient for me to have my formulas in the object itself, but right now it severely affects the performance.
You can try to add
[NotMapped]
public int FormulaA ....
And also in query select only properties what you really need.
select new {e.PropA, e.PropB}

Fluent nHibernate , IDictionary Confusion

Using the following classes..
public class Trait
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Sheet
{
public virtual int Id { get; set; }
public virtual IDictionary<Trait, int> Influences { get; set; }
}
I have tried to map them using Fluent nHibernate, as such.
public class TraitMap : ClassMap<Trait>
{
public TraitMap()
{
Id(x => x.Id);
Map(x => x.Name);
Table("Traits");
}
}
public class SheetMap : ClassMap<Sheet>
{
public SheetMap()
{
Id(x => x.Id);
HasManyToMany<Trait>(x => x.Influences)
.Schema("Sheets")
.Table("Influences")
.ParentKeyColumn("Trait")
.ChildKeyColumn("Sheet")
.AsMap<int>("Rating")
.Cascade.All();
Table("Sheets");
}
}
This does not work. I get the exception.
Exception occurred getter of Trait.Id
Now, if I change the Dictionary up to look like this..
public class Sheet
{
public virtual int Id { get; set; }
public virtual IDictionary<int, Trait> Influences { get; set; }
}
Basically making the int the Key, and the Trait the value (not what I want), it does work. Can anyone explain this, and how I can more appropriately reproduce what I am trying to do?
I think the reasoning is because when I specify HasManyToMany<Trait> I am specifying the Value element of the collection. However this is not my intention.
I want to look things up by the Name of the Key, not the Name of the Value. While I realize this is technically an 'acceptable' solution, it kind of goes against the Dictionary convention. I'd prefer to take a more convention over solution approach, if at all possible - and I'd like to better understand what is actually going on under the hood.
What you want is what <composite-index> mapping in hbm would give you. I believe that instead of AsMap, you'd want AsEntityMap. However, look at this thread which talks about a rewrite (in august) of the fluent mapping for Maps that makes all of the above obsolete.
EDIT : For AsEntityMap, and other options, take a look at this SO question and answer
HasMany<Trait>(x => x.Influences)
.KeyColumn("key column name")
.AsEntityMap("referenced column name")
.Entity("dict value", v=> v.Type<int>());
Also, you say you're on the latest version - the latest release is 1.1, but trunk is 2.0, and is very different, and much further along. If you're not on 2.0+, you wouldn't see the methods posted in most of the help threads like the one linked above.

Categories