Update only mapped fields or Ignore the unmapped fields - Automapper? - c#

I'm very new to Automapper and I'm facing difficulties in Updating mapped fields. I must agree that there are millions of answers for this question in internet but the problem is nothing helps for my scenario.
Issue:
I'm trying to update only the mapped fields. But when I do that, my complete destination properties gets updated.
For e.g.,
My destination object(DB Table) holds 5 properties and all the 5
properties accepts null. But I have only 3 properties in my
source(Model file). I'm mapping all the three source fields with the
destination fields but I don't have mapping for the remaining two
destination properties. You would have guessed it correctly, yes when
I update this, that two unmapped fields are also getting update to null(default value - DB). But it
should not be the case, rather it should have the existing values.
Kindly see my code below,
References:
Automapper Version: v6.2.2 and Runtime Version: v4.0.3
Repository.cs,
var objectToUpdate = Mapper.Map<TDomain>(entity); //TDestination Map<TDestination>(object source);
DatabaseContext.Entry(objectToUpdate).State = EntityState.Modified;
InvoiceLineMapping.cs
public static void Map(IProfileExpression profile)
{
profile.CreateMap<Registration, PT_Registration>()
.ForMember(d => d.Name_VC, map => map.MapFrom(s => s.Name))
.ForMember(d => d.UserName_VC, map => map.MapFrom(s => s.Username))
.ForMember(d => d.Mobile_VC, map => map.MapFrom(s => s.Mobile));
}
Note: As I said before, I have two unmapped fields are there in the destination and they are EMail_VC and Comments_VC and their values are sample#test.com and testComments respectively. When update happens, these two fields becomes null(default value)
I tried,
.ForAllOtherMembers(opt => opt.Ignore()); //after the last mapping i.e Mobile
profile.CreateMap(MemberList.Source) // I also tried MemberList.Destination
I also tried, AutoMapper: "Ignore the rest"? extenstion method,
I tried puttin ReverseMap() in the end. //after the last mapping i.e Mobile
I even tried to find the null value and removing from destination object(objectToUpdate - you can see above) but nothing helps.
So can someone please look into this and provide me a better solution. I knew solution would be simpler but as I'm not aware of it, it takes more and more time.
I appreciate your time. Any help would be great helpful.

Related

Is there a way to dyanmically create multiple maps for Automapper?

Is there a way to dynamically generate multiple maps? For example, can I map to an array of source and destination values? I am working with a legacy database and one of the tables has over 350+ columns. It will be very error prone and labor intensive to map each field manually.
//HOW CAN BELOW INCLUDE OVER 350+ MAPPED FIELDS DYNAMICALLY FROM AN ARRAY?
CreateMap<Employee, EditEmployeeModel>()
.ForMember(dest => dest.ConfirmEmail,
opt => opt.MapFrom(src => src.Email));
Basically if the property names are similar then you don't have to map source to destination manually. Automapper will do this automatically. So, if you keep the property names similar then there's no need to map.
But if that's not possible and keeping different names is a necessity then you can use .AfterMap() feature of Automapper. It is simpler than source destination mapping.
public class EditEmployeeMap : IMappingAction<Employee, EditEmployeeModel>
{
public void Process(Employee source, EditEmployeeModel destination, ResolutionContext context)
{
destination.ConfirmEmail = source.Email;
}
}
Then,
CreateMap<Employee, EditEmployeeModel>().AfterMap<EditEmployeeMap>();
Though the purpose of the above solution is not for this use case, this can make the job more simple for you.
But i would suggest to keep the property name similar, that will save a lot of hastle.

Automapper Collection

I'm working on a project and I need to map collections. I came across Automapper.Collection and am trying to user the class but its all not working. Please I need help. Here is my code.
In my startup class
services.AddAutoMapper(cfg => { cfg.AddCollectionMappers(); },typeof(Startup));
I also created a class that Inherits from the Automapper Profile class here
public class UserMappingProfile : Profile
{
public UserMappingProfile()
{
CreateMap<PhotoToUpdateDto, Photo>().EqualityComparison((src, dest) => src.PublicId.ToLower() == dest.PublicId.ToLower());
CreateMap<SocialHandles, SocialHandleDto>().ReverseMap().EqualityComparison((src, dest) => src.Name.ToLower() == dest.Name.ToLower());
}
}
Anytime I use the mapper, it creates new records in the database instead of updating the already existing. Please I need help.
I think its because of navigation property in your entity
If you have navigation property ignore them in your Createmap
For example
CreateMap<PhotoToUpdateDto, Photo>().EqualityComparison((src, dest) =>
src.PublicId.ToLower() == dest.PublicId.ToLower())
.ForMember(p => p.Photographer , opt => opt.Ignore());
I'll assume this:
You have no problem with the mapper (since it still map your object successfully).
When you get new object to interact with database, a new record created, since your make sure the id mapping was correct.
You are using EF Core or some kind of ORM to interact with the database
If i was wrong, please just ignore this answer.
Okay, If my assuming was right, that's because automapper always return a new object as result of the mapping process, which is absolutely not tracked by your DbContext yet. And then, whenever you interact with that to make some change in the database, an add operation is likely going to happen.
I think you need to add some more information about the block of code that you actually have problem with... which in this case... why the database entry keep created, but not update.

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.

Copy an Entity Framework graph leads to unwanted duplicates

I’m trying to copy/clone entity graph with EF6.1 and getting duplicate entities.
Below is a piece of my model which consist of a Template that I want to modify, copy and assign to different users, something like Save As function.
Here is my entities model:
What I’m doing is:
var newTemplate = ctx.Templates
.Where(t => t.TemplateId == SelectedTemplate.TemplateId)
.Include(t => t.Properties.Select(p => p.PropertyCollections))
.Include(t => t.Properties.Select(p => p.Values))
.AsNoTracking()
.First();
newTemplate.TemplateName = newTemplateName;
ctx.Templates.Add(newTemplate);
ctx.SaveChanges();
And what I get is shown below where “Template1” is the source and “Template2” is the copy in which every ‘PropertyCollection’ has a duplicated entry for each ‘Property’.
Result after copy:
I understood that with AsNoTracking there is no identity mapping which is the reason behind this but I can’t find even a custom solution.
I didn't really test your code, but I think your Entities might really get messed up when doing it that way. Maybe this attempt would work for you. It's for EF4 but might work.
You are adding the whole graph, so EF is inserting everything. You are using AsNoTracking to "trick" EF instead of its original purpose.
I would suggest you to write a few lines of code to actually implement your business requirement, which is create a new Template based on another one.
So, get the template (without the AsNoTracking), and create a new template initializing the properties based on the original template values. Then add the new template to the context. EF will insert the new template and reference the existing dependent entities.
This is also a safer way to implement this, as in the future you might require to set some properties with different values in the new template.

Entity Framework issues - appends a "1" to my table name?

I have the following model-first (is that what it's called?) diagram that I have made. I use T4 to generate the classes.
Now, I have a problem that causes Entity Framework to somehow append a "1" to the table name of the DatabaseSupporter entity. The database has been generated from this very model, and nothing has been modified.
I am trying to execute the following line:
_entities.DatabaseSupporters.SingleOrDefault(s => s.Id == myId);
The error I receive when executing that line (along with its inner exception below) is:
An exception of type
'System.Data.Entity.Core.EntityCommandExecutionException' occurred in
mscorlib.dll but was not handled in user code.
Invalid object name 'dbo.DatabaseSupporter1'.
I tried fixing the problem with the following Fluent API code (notice the second line in the function that names the table explicitly to "DatabaseSupporter"), but with no luck.
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<DatabaseSupporter>()
.HasOptional(f => f.DatabaseChatSession)
.WithOptionalPrincipal(s => s.DatabaseSupporter);
modelBuilder
.Entity<DatabaseSupporter>()
.Map(m =>
{
m.Property(s => s.Id)
.HasColumnName("Id");
m.ToTable("DatabaseSupporter");
});
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseGroups_Id");
m.MapRightKey("DatabaseSupporters_Id");
});
modelBuilder
.Entity<DatabaseGroup>()
.HasRequired(g => g.DatabaseChatProgram)
.WithMany(c => c.DatabaseGroups);
modelBuilder
.Entity<DatabaseGroup>()
.HasRequired(g => g.DatabaseOwner)
.WithMany(o => o.DatabaseGroups);
modelBuilder
.Entity<DatabaseOwner>()
.HasMany(o => o.DatabaseChatSessions)
.WithRequired(o => o.DatabaseOwner);
base.OnModelCreating(modelBuilder);
}
It should be mentioned that the Id property for every entity actually is a Guid.
I am using Entity Framework 6.0.2.
Any ideas?
Edit 1
Here's the generated DatabaseSupporter.cs file containing my DatabaseSupporter entity as requested in the comments.
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Coengage.Data.Entities
{
using System;
using System.Collections.Generic;
public partial class DatabaseSupporter
{
public DatabaseSupporter()
{
this.DatabaseGroups = new HashSet<DatabaseGroup>();
}
public bool IsActive { get; set; }
public string Username { get; set; }
public System.Guid Id { get; set; }
public virtual DatabaseChatSession DatabaseChatSession { get; set; }
public virtual ICollection<DatabaseGroup> DatabaseGroups { get; set; }
}
}
Edit 2
The errors started occuring after I added the many-to-many link between DatabaseSupporter and DatabaseGroup. Before that link, the Fluent code wasn't needed either.
This mapping is incorrect:
modelBuilder
.Entity<DatabaseSupporter>()
.Map(m =>
{
m.Property(s => s.Id)
.HasColumnName("Id");
m.ToTable("DatabaseSupporter");
});
It is kind of 50 percent of a mapping for Entity Splitting - a mapping that stores properties of a single entity in two (or even more) separate tables that are linked by one-to-one relationships in the database. Because the mapping is not complete you even don't get a correct mapping for Entity Splitting. Especially EF seems to assume that the second table that contains the other properties (that are not explicitly configured in the mapping fragment) should have the name DatabaseSupporter1. I could reproduce that with EF 6 (which by the way has added a Property method to configure single properties in a mapping fragment. In earlier versions that method didn't exist (only the Properties method).) Also the one-to-one constraints are not created correctly in the database. In my opinion EF should throw an exception about an incorrect mapping here rather than silently mapping the model to nonsense without exception.
Anyway, you probably don't want to split your entity properties over multiple tables but map it to a single table. You must then replace the code block above by:
modelBuilder.Entity<DatabaseSupporter>()
.Property(s => s.Id)
.HasColumnName("Id");
modelBuilder.Entity<DatabaseSupporter>()
.ToTable("DatabaseSupporter");
The first mapping seems redundant because the property Id will be mapped by default to a column with the same name. The second mapping is possibly also redundant (depending on if table name pluralization is turned on or not). You can try it without this mapping. In any case you shouldn't get an exception anymore that complains about a missing dbo.DatabaseSupporter1.
I have replicated your model exactly as you have listed it and I cannot currently reproduce your issue in the DDL that the EDMX surface emits when Generating Database from Model.
Could you please provide detailed information on exactly how you are going about adding your many-to-many relationship between DatabaseGroup and DatabaseSupporter? You say that you're trying to add the relationship on the edmx surface and NOT through code and it craps on your table name?
I added this thing Many-to-many from DatabaseGroup to DatabaseSupporter
I added this thing Many-to-many from DatabaseSupporter to DatabaseGroup
Can you please provide the following:
Rollback to your codebase prior to adding the many-to-many relationship. Ensure that your EF Fluent API code is not currently in your project.
Generate the DDL from this surface and confirm that it is not being
generated with the name DatabaseSupporters1 (Post the tablename that
it chooses at this stage. DatabaseSupporter or DatabaseSupporters)
Now, right click DatabaseGroup| Add New| Association
Choose DatabaseGroup for the left and DatabaseSupporter for the
right. Confirm that the name of the association that the designer
chooses is DatabaseGroupDatabaseSupporter [Do not create]
Choose DatabaseSupporter for the left and DatabaseGroup for the
right. Confirm that the name of the association that the designer
chooses is DatabaseSupporterDatabaseGroup [Create]
From the edmx surface, right click the many-to-many association just created and click "Show in Model Browser"
Edit your post to include the settings that display.
Also, right click the surface and click "Generate Database from Model."
Edit your post to include the DDL that gets generated. The table
should be named [DatabaseSupporters]
(My first inclination is that it's going to have something to do with your navigation properties, but not entirely sure. I actually had Entity Framework do the same thing to me in a toy project I was working on but I recall it being trivial to correct and I don't recall what the root cause was; I seem to recall it being something about the nav properties)
[Edit]
Wait.....
If I remove the many-to-many that doesn't fix my problem. However,
reverting to before I added the many-to-many fixes it. The exact code
that throws the exception is already shown. If I remove my fluent
mappings entirely, it's not the same exception being thrown (it throws
something about a group and a supporter, and a principal). I have not
tried recreating the model in an empty project - that takes a lot of
time. I already tried searching the EDMX in Notepad for references -
none were found.
(note my added emphasis)
So the DatabaseSupporter1 error showed up after you tried your fluent api patch? Get rid of the patch, add the many-to-many and give us the real error then.
...also, it took me 5 minutes to build this diagram. I wouldn't qualify that as "a lot of time."
I don't have my dev environment here in front of me, but my immediate thoughts are:
FIRST
Your fluent looks ok - but is the plural s in your ID column correct? And no plural (s) on the table names? This would be the opposite of convention.
SECOND
EF will automatically append a number to address a name collision. See similar question here: Why does EntityFramework append a 1 by default in edmx after the database entities?
Any chance you have something hanging around - a code file removed from your solution but still in your build path? Have you tried searching your source folder using windows explorer rather than the visual studio?
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseGroups_Id");
m.MapRightKey("DatabaseSupporters_Id");
});
Left and Right are inversed on Many to Many.
Try this :
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseSupporters_Id");
m.MapRightKey("DatabaseGroups_Id");
});
I think the DatabaseSupporter class created two time
one name is : DatabaseSupporter
another one is : DatabaseSupporter1
The modified changes are stored in DatabaseSupporter1 and mapping to here.
You need to copy the DatabaseSupporter1 class code and past the code to DatabaseSupporter class . then delete this DatabaseSupporter1 class.
I had this issue from renaming tables in the diagram, specifically changing just the capitalization.
If you rename a table by clicking on the header in the diagram, I think it checks the entity set name before trying to change it, sees it exists (even though it's the same entity set), and appends a 1.
However, if you right-click and open the Properties pane and first rename the Entity Set Name, then change the Name second, it won't add the number.
In my case i have two tables in the same database with the same name (2 different schemas(see image)

Categories

Resources