How can I use AutoMapper to map one collection to another? - c#

I would like to use AutoMapper to map one collection to another. I know that the convention is to set up a mapping for the child object:
Mapper.CreateMap<User, UserDto>();
And then this works fine:
Mapper.Map<List<UserDto>>(UserRepo.GetAll());
But I'd still like to map the list anyway. For example, I'd like to do something like this:
Mapper.CreateMap<List<User>, List<UserDto>>()
.AfterMap((source, destination) =>
{
// some complex/expensive process here on the entire user list
// such as retrieving data from an external database, etc
}
That way I can still use the first map but also to do something custom with the user list as well. In my scenario, it's looking up event IDs in an external database in another datacenter and I'd like to optimise it by only looking up unique IDs, not doing it object-by-object.
However, when I attempt to map a list of User to a list of UserDto, the mapping just returns an empty list. Putting a breakpoint in the AfterMap function reveals that the destination variable holds an empty list. How can I make AutoMapper do this correctly?

Well, that was embarrassing. Five minutes later, I just figured out that you can add a ConvertUsing function which just proxies to a member-by-member mapping:
Mapper.CreateMap<List<User>, List<UserDto>>()
.ConvertUsing(source =>
{
// some complex/expensive process here on the entire user list
// such as retrieving data from an external database, etc
return source.Select(Mapper.Map<User, UserDto>).ToList());
});
Perhaps that's not how you're meant to do it but it works for me.

Related

I need to filter based of off objects that are nested inside a list of objects

So all of my other filters work correctly, so I know it is just for the nested objects. But I am not sure how to approach it. So simply put the data looks like this (there are many more columns, but this gets the point across):
[{firstName:"Tom", lastName:"Smith", Sites:[{siteName:"Washington"}, {siteName:"Arkansas"}, {firstName:"Mary", lastName:"Smith", Sites:[{siteName:"Washington"}]
The site details are nested inside the the users details, this is because a user can belong to many sites. I need to be able to show all customers that belong to a specific site. And if I filter by first or last name it works, but my logic for siteName is wrong.
{FilterOperation.Contains, (usersQuery) => { return usersQuery.Where(c => c.Sites.Any(s => s.SiteName.ToLower() == this.Value.ToLower())); } }
For some reason the data is not being passes inside the Any for the comparison. It is acting like Sites is empty. The only guess I have is that I need to create a separate filter for site objects that is called from within this filter??
Looks like you are using ef core for usersQuery and Sites being navigation property to the User. If it is the case it is because of lazy loadign feature in ef core, you need to add .Include(user=>user.Sites) when the query is formed to load Sites as children.

Prepare data befire RuleForEach in FluentValidation

I'm trying to validate model which contains list of ids. There is few simple business rules like 'is it exists, is type is correct' and so on. I wanted to create internal Validator for single item (which is Id) but to avoid asking db for each of entity one by one I'm trying to get all of them and put on dictionary which later I want to pass to internal validators to get concrete entity by id.
RuleForEach(m => m.BunchOfIds)
// how to get it?
.SetValidator(new InternalValidator(_entities));
Any idea? I've tried to create CustomAsync but then I cannot use 'RuleForEach' but for now it looks like only possible option

Mapping List<dynamic> using Slapper

I've got the following code snippet in a repository class, using Dapper to query and Slapper.Automapper to map:
class MyPocoClass{
MyPocoClassId int;
...
}
//later:
var results = connection.Query<dynamic>("select MyPocoClassID, ...");
return AutoMapper.MapDynamic<MyPocoClass>(results).ToList();
results above has many items, but the list returned by AutoMapper.MapDynamic has only one item (which is clearly wrong). However, I found that adding the following configuration to AutoMapper fixes the problem:
AutoMapper.Configuration.AddIdentifier(typeof(MyPocoClass), "MyPocoID");
Why does Slapper.AutoMapper need to know the key of my class to simply map a list to another list? Is it trying to eliminate duplicates? I'll also note that this only happens while mapping a certain one of my POCOs (so far)...and I can't figure out why this particular POCO is special.
Turns out this is a bug in Slapper.AutoMapper.
The library supports case-insensitive mapping and convention-based keys. The SQL result set has MyPocoClassID and the class itself has MyPocoClassId -- which is not a problem for Slapper.AutoMapper as far as mapping goes. But internally Slapper.AutoMapper identifies (by convention) that MyPocoClass has MyPocoClassId as its identifier, and it can't find that field in the result set. The library uses that key to eliminate duplicates in the output list (for some reason), and since they're all 'null/empty', we get only one record.
I may submit a pull request to fix this problem, but since the library appears to be unmaintained I don't think it'll help.

AutoMapper: ignore property on update existing, but allow when create

Is it possible to configure/use AutoMapper in such a way where when i create an object from a mapping i allow all properties and child collections, however, when it comes to performing an update to existing object, the mapping will ignore child collection properties as they will be empty but i dont want them removed.
This is because i am working with a WCF service that sends delta changes to objects and most of my model works in a tree hierarchy:
Parent
List<Child> Children
ParentDto
List<ChildDto> Children
config.CreateMap<ParentDto, Parent>();
config.CreateMap<ChildDto, ChildDto>();
This works well and the child collection is populated first time round. However, there are scenarios where i will send the ParentDto across with just the parent POCO property changes (such as a datetime change), but the child list will be empty as none of them have changed. Normally i would do:
_Mapper.Map<ParentDto,Parent>(dto, local)
but obviously that will change the entire tree and populate the local object with an empty child list. Massively simplifying but would something like
_Mapper.Map<ParentDto, Parent>(dto, local).Ignore(p => p.Children)
be possible?
I should also add I am using SimpleInjector DI framework. So perhaps there is a way to register 2 configurations, one with ignore and one without?
Use .ForMember(dest => dest.A, opt => opt.MapFrom(src => src.B)) for mapping only properties you need to update.
For those who still struggle to find this. You can use Autommapper Conditional Mapping.
You can do it like this, in the Initialize
config.CreateMap<ChildDto, ChildDto>().ForMember(dest => dest.Children, opt => opt.Condition(source => source.TriggerChildMap));
This will ignore mapping based on the property in source object. To map against existing destination you need to use
Mapper.Map(source, destination) method and not the var result = Mapper.Map<ChildDto>(source) property.

Not able to remove items from database when user deletes a row in data grid

When a user hits the button, I'm executing the following code.
using (Context context = new Context())
{
foreach (Thing thing ViewModel.Things)
context.Things.AddOrUpdate(thing);
context.SaveChanges();
}
The updates are executed except for when the user selected a row and hit delete button. Visually, that post is gone but it's not really being removed from the database because it's not in the view model anymore. Hence, the loppification only ticks for the remaining things and not touching the removees.
I can think of two ways to handle that. One really bad - to remove everything from the context, save it and then recreate based on the view model. It's an idiotic solution so I'm only mentioning it for the reference's sake.
The other is to store each removed post in an array. Then, when the user invokes the code above, I could additionally perform the deletion of the elements in that array. This solution requires me to build the logic for that and I'm having this sense that it should be done automagically for me, if I ask nicely.
Am I right in my expectation and if so, how should I do it? If not, is there a smarter way to achieve my goal than creating this kill squad array?
At the moment, I do a double loop, first adding and updating what's left in the data grid. Then, removing anything that isn't found there. It's going to be painful if the number of elements grows. Also, for some reason I couldn't use Where because I need to rely on Contains and EF didn't let me do that. Not sure why.
using (Context context = new Context())
{
foreach (Thing thing in ViewModel.Things)
context.Things.AddOrUpdate(driver);
foreach (Thing thing in context.Things)
if (!ViewModel.Things.Contains(thing))
context.Things.Remove(thing);
context.SaveChanges();
}
The first thing I want to advice you is you should use the AddOrUpdate extension method only for seeding migrations. The job of AddOrUpdate is to ensure that you don’t create duplicates when you seed data during development.
The best way to achieve what you need you can find it in this link.
First in your ViewModel class you should have an ObservableCollection property of type Thing:
public ObservableCollection<Thing> Things {get;set;}
Then in the ViewModel's constructor (or in another place), you should set the Things property this way:
context.Things.Load();
Things = context.Things.Local;
From the quoted link:
Load is a new extension method on IQueryable that will cause the
results of the query to be iterated, in EF this equates to
materializing the results as objects and adding them to the DbContext
in the Unchanged state
The Local property will give you an ObservableCollection<TEntity> that
contains all Unchanged, Modified and Added objects that are currently
tracked by the DbContext for the given DbSet. As new objects enter the
DbSet (through queries, DbSet.Add/Attach, etc.) they will appear in
the ObservableCollection. When an object is deleted from the DbSet it
will also be removed from the ObservableCollection. Adding or Removing
from the ObservableCollection will also perform the corresponding
Add/Remove on the DbSet. Because WPF natively supports binding to an
ObservableCollection there is no additional code required to have two
way data binding with full support for WPF sorting, filtering etc.
Now to save changes, the only you need to do is create a command in your ViewModel class that call SaveThingsChanges method:
private void SaveThingsChanges()
{
context.SaveChanges();
}

Categories

Resources