C# Automapper Nested Object - c#

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

Related

Automapper flattening not behaving as expected

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.

How do I clone an object with virtual navigational properties in it, using memberWiseClone in c#

I have tried to clone an object using memberWiseClone(). It will clone all the value type properties successfully, but will not clone the reference type properties.
The class I'm trying to clone.
public class Document
{
public int OwnerId { get; set; }
public int DocumentManagerId { get; set; }
public virtual User DocumentManager { get; set; }
public Document ShallowCopy()
{
return (Document)this.MemberwiseClone();
}
}
Getting all the correct values for OwnerId and DocumentManagerId except DocumentManager
shallowCopy()is called in my service class update method.(Keeping the state of previous document to log after the document is actually updated)
var document = await this.dbContext.Documents.FirstOrDefaultAsync(x => x.id == documentDto.id);
var prevDocument = document.ShallowCopy();
Im getting the correct result for document.DocumentManager while not getting it for prevDocument.DocumentManager. Therefore, this should not be due to any configurations in entity mapping. I wanna know why the virtual object throws an exception when memberWiseCloning.
Workaround used at the moment
I have returned a new instances of both the objects with values manually mapped.
Also i know i can go for deep cloning by serializing the value to memory stream. But i prefer if i can find a better way from memberWiseClone approach.
Thanks in advance!

Is it possible to access the parent of a Linq object?

In a situation where an EntitySet is being queried (in, say, a many-to-many relationship), is it possible to access the parent object?
e.g
Thing thing = db.Things.First();
Widget widget = thing.Widgets.First();
// Let's assume that Widgets can have many things as well
// (i.e. widget.Things is possible)
widget.ParentThing // would return the same instance of thing used above
Is that possible?
is it possible to access the parent object
With a many-to-many there really isn't a "parent" - there are multiple related objects. A many-to-many is usually modeled with navigation properties:
public class Thing
{
public int ID {get; set;}
public virtual IEnumerable<Widget> Widgets {get; set;}
}
public class Widget
{
public int ID {get; set;}
public virtual IEnumerable<Thing> Things {get; set;}
}
If your model doesn't have such properties then an alternative is to go back to the context (or back to the db if you don't have the context anymore):
var relatedThings = db.Things
.Where(t => t.Widgets.Any(w => ID == widget.ID));

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.

How do I set a default binding property for a parent object property?

I'm running into a ton of something that looks like this:
public class TestClass
{
public string Property1 {get;set;}
public string Property2 {get;set;}
public AddressType Type {get;set;}
[RequiredIf("", "", ErrorMessage="...")]
public int TypeId
{
get
{
if (Type == null)
return 0;
else
return Type.Id;
}
set
{
Type = new AddressType() { Id = value };
}
}
}
public class AddressType
{
public int Id {get;set;}
public string Description {get;set;}
}
so that I can model bind the info in a razor form and get binding back and forth. I'm wondering if there is anything out there that anyone knows where I can either apply an attribute against "AddressType" to set the default binding property for the class, or put an attribute against the 'Type' field of TestClass and say "When you bind, really bind to Type.Id, but validate against the whole object otherwise...
Not sure if I'm asking this right, but I just want a cleaner implementation if at all possible... I feel like adding the TypeId is unnecessary cruft in the class, and it makes it difficult to read.
Thx Guys!
Ok I'm not 100% across what you are asking here but I'll give it a go by assuming AddressType is nullable.
public class TestClass
{
public AddressType? Type {get;set;}
public int TypeId
{
get
{
return Type.HasValue ? Type.Value.Id : 0;
}
}
}
Looking at AddressType though I'm guessing that its a lookup type sourced from a datastore of some kind. I use T4 templates to generate these kinds of lookup lists (that don't change values between releases) as enums in my apps. Which if you do it will relieve you of a bunch of stress.
Now if what you want is a dropdownlist of AddressType values in your razor view you're going to have to do a little big of work in the controller (Not much thankfully)
public class BetterTestClass
{
public AddressType? Type {get;set;}
}
...In your AddressController
public ActionResult Create(){
// the name in the ViewBag should match
// the property you want to have a list on
ViewBag.Type = repository
.AddressTypes
.ToList()
.Select(p => new SelectListItem {
Key = p.Id,
Value = p.Description});
ViewData.Model = new BetterTestClass();
return View();
}
There are plenty of examples around if you search for #Html.DropDownList
EDIT
It's really difficult to work out what you are trying to achieve by your question. But I'll try and help. First do I understand your question correctly:
TestClass has a 0..1 relationship with AddressType
Address Type has an Id because it is an Entity class
For some reason you want the Id of AddressType to be set via the UI when AddressType is set. (which I outlined in my answer the inference being that you won't need required attributes)

Categories