I try to move my project to AutoMapper 9.x but I don't find a solution to update an object read from EF.
The situation is like as describe on this old post Using Automapper to update an existing Entity POCO
The big issue is that:
AutoMapper 9.x has removed the static Mapper.Map and you can add the [AutoMap(typeof(x))] attribute but it solve the mapping from EF Class to DTO class.
class MyPoco{
public int Id {get;set;}
}
[AutoMap(typeof(MyPoco))]
public class Customer {
public int Id { get;set; }
}
MYPOCO pocoDesc= dbContext.DD_POCO.SingleOrDefault(m => m.Id == 123);
//Row below not supported
AutoMapper.Mapper.Map<Customer, MYPOCO>(customerSrc, pocoDesc);
dbContext.Save();
At the moment the only way to solve is remove AutoMapper and manually bind one by one the property.
UPDATE
After some test I try to compare to call
ObjectMapper.Map(input.Article, article);
Mapper.Instance.Map(input.Article, article);
ObjectMapper came from DI. In startup module I register via IMapperConfigurationExpression
mapper.CreateMap<Customer,MyPoco>().ConvertUsing((src,dto) =>{
// remove for brevity
});
And I see that static API not pass throw my custom map opposite to ObjectMapper that use my custom map. At the moment I don't understand why but I try to investigate more
Related
TL;DR: Is there a way to create a sort of convention or prefix rule that would use the source or destination class names as prefix for some field mappings using AutoMapper 12.0?
I'm experimenting with AutoMapper 12.0 in a dotnet core 6 project and the entity classes use the entity name as prefix for their keys (eg.: Person class has a PersonId property, which is mapped to the primary key in DB), but the API side has DTOs that don't include those prefixes:
// Entity definition
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
// ...
}
// API response definition
public class PersonDetails
{
public int Id { get; set; }
public string Name { get; set; }
// ...
}
The way I configured my mappings to make it work was to manually define the mapping for the IDs using .ForMember(...), but that requires me to do that for every single entity and DTO combination:
public class EntityToViewModelMappingProfile : Profile
{
public EntityToViewModelMappingProfile() : base(nameof(EntityToViewModelMappingProfile))
{
CreateMap<Person, PersonDetails>()
.ForMember(p => p.Id, o => o.MapFrom(p => p.PersonId))
.ReverseMap();
// ...
// the same code for all of my entities :(
}
}
I'm looking for a way to avoid a bunch of boilerplate code and have a generic rule that would work for all of my entities without having to write custom mappings for all of them.
Even though I have custom mappings for other fields, that case is the one that seems to be a fit for a custom convention, but I couldn't find a way to implement one that would give me access to the destination and source type names to be able to use them as prefixes.
As Lucian Bargaoanu pointed out in the comments, there is a similar question for an earlier version of AutoMapper here and it suggests using:
Mapper.Initialize(exp =>
{
exp.CreateMap<User, UserDto>();
exp.ForAllMaps((typeMap, mappingExpression) =>
mappingExpression.ForMember("Id", o=>o.MapFrom(typeMap.SourceType.Name + "Id"))
);
});
AutoMapper 12.0 seems to not support ForAllMaps, from what I researched in their docs. Is there a way to achieve the same with 12.0?
Lucian Bargaoanu's comments lead me to a similar question for a previous version of AutoMapper, but the current version (12.0) doesn't expose that API through the regular public interface anymore. At least it gave me a hint on possible ways of achieving my problem, so I started looking for how to apply the same on newer versions.
By digging through the upgrade docs from past versions, I found that ForAllMaps was moved to the Internal API on version 11.0 and now requires importing AutoMapper.Internal namespace to have access to the .Internal() extension methods on config objects to access that method.
Here is my solution for the problem in AutoMapper 12.0 (possibly compatible with 11.0 as well):
using AutoMapper;
using AutoMapper.Internal; // <== this is important
namespace MyOwn.Solution.AutoMapper
{
public class DomainToAPIModelMappingProfile : Profile
{
public DomainToAPIModelMappingProfile() : base(nameof(DomainToAPIModelMappingProfile))
{
CreateMap<Domain.Entities.Person, APIModels.PersonDetails>();
CreateMap<Domain.Entities.Foo, APIModels.FooData>();
CreateMap<Domain.Entities.Bar, APIModels.BarData>();
// ...
// this is the solution
this.Internal().ForAllMaps((typeMap, mappingExpression) =>
mappingExpression.ForMember("Id", o => o.MapFrom(typeMap.SourceType.Name + "Id"))
);
}
}
}
I would like to ask how to update row that previously was stored as some specific type and should be updated to another specific type.
Let's assume I have configured Entity Framework to use Table Per Hierarchy inheritance strategy. Now, let's say I have these classes:
abstract class Package
{
public string SomeSharedValue { get; private set; }
}
class PublicPackage : Package
{
public int SomeProperty1 { get; private set; }
public Package TurnIntoPrivatePackage(int someProperty2)
{
return new PrivatePackage(someProperty2);
}
}
class PrivatePackage : Package
{
public int SomeProperty2 { get; private set; }
public Package TurnIntoPublicPackage(int SomeProperty1)
{
return new PublicPackage(SomeProperty1);
}
}
and I have configured my model in such a way:
modelBuilder.Entity<Package>(m =>
{
m.HasDiscriminator<int>("Type")
.HasValue<PublicPackage>(1)
.HasValue<PrivatePackage>(2);
});
so right now, how do I turn (update) let's say PublicPackage into PrivatePackage
Would it work if I do something like:
public async Task DoSomething(DbContext dbContext, Guid packageId){
var package = dbContext.Packages.SingleOrDefaultAsync(f => f.Id == packageId);
//now package is of type PublicPackage
var updatedPackage = package.TurnIntoPrivatePackage(someValue)
//updated package has the same Id and other values setted for private package right now but it's new (another) instance with the same id.
dbContext.Update(updatedPackage); // Can I do this? should I detach the previous instance?
await dbContext.SaveChangesAsync()
}
Ok, I've got enough of an idea now what you're trying to accomplish.
Problem
You'd like to use table per hierarchy to abstract 2 different types of packages using EF, and you want to know how to change from one package type to another, or in database terms, set the discriminator value to the new value and update the object accordingly.
Solution
You can't directly or explicitly set the discriminator to another value. The github issue here will explain further: https://github.com/dotnet/efcore/issues/7510
In the issue above you can go with their example to work around that to set the discriminator explicitly by setting it meta data property in the model builder. But direct transmutation from one object to another is not supported. Doesn't appear like they'll ever support it.
You'd have to add the following code:
modelBuilder.Entity<Package>()
//EF Core version 2.0.0 syntax
.Property("Type").Metadata.AfterSaveBehavior = PropertySaveBehavior.Save
I'm not sure if I'd go this route though unless I was dealing with data that I new was going to be static as far as its types. This problem would make me rethink the approach in this use case.
I am trying to update an entity using Entity Framework and save it to the database. When the update is called, my service method retrieves the DTO, assigns its the values of the entity object that the UI passed to it, and then saves it to the database. Instead of manually assigning those values, i'd like to use Automapper, however when I do this, the values that I am not mapping are updated to null. Is there a way in Entity Framework or Automapper to prevent this?
Service method finds the existing object in the database, assigns the new entity's properties to it, then saves:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
existingObject.name = updatedEntity.name;
existingObject.address = updatedEntity.address;
existingObject.phone = updatedEntity.phone;
db.SaveChanges();
}
However, there are values stored in fields of this object not accessible by the UI, such as who modified the object and when. Using AutoMapper to simplify this code (shown below) causes these fields to update to null:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
Mapper.Map(updatedEntity, existingObject);
db.SaveChanges();
}
A good practice is to create a (service, api) model that contains only the relevant properties that can be updated. E.g.:
public class MyEntityServiceModel
{
public string name { get; set; }
public string address { get; set; }
public string phone { get; set; }
}
// this looks differently in recent versions of AutoMapper, but you get the idea
Mapper.CreateMap<MyEntityServiceModel, MyEntity>();
// your update functions looks the same, except that it receives a service model, not a data model
Update(MyEntityServiceModel updatedEntity, int id)
{
// same code here
}
This approach has the following advantages:
you obtain what you are asking for
safety: you do not risk updating more properties than you should since the service model clearly specify the properties that should be updated
serialization: the service model is more appropriate if you need serialization (EF models may include unwanted navigation properties)
Update function consumer becomes unaware of the data persistence library you are using.
On the server side I have an Entity Framework DbContext NorthwindContext and an System.Data.DataService<NorthwindContext>
My client needs to query the dataservice. I am able to retrive data with
var uriString = "http://localhost:8888/northwind.svc";
var context = new DataServiceContext(new Uri(uriString));
var query = context.CreateQuery<orders>("orders");
var result = query.ToList();
Ok so far this works. I don't use the visual studio tool chain for client proxy / class generation but create my own "copy" of the orders class which looks like this
[DataServiceKey("OrderID")]
public partial class orders
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
...
}
This only works because I specified the DataServiceKey attribute. If I don't do that, I get an DataServiceQueryException
DataServiceKey is from Microsoft.Data.Service.Client which is referenced by my main assembly my model orders is in a seperate assembly where I don't want to have a reference to Microsoft.Data.Service.Client
Is it possible to use my query without needing to decorate my class with DataServiceKey attribute?
You cannot remove DataServiceKey from the entity you declare.
•Entity keys - each data class that is an entity type must have a key property. This property is attributed with the DataServiceKeyAttribute attribute ([DataServiceKeyAttribute]).
Link:
http://msdn.microsoft.com/en-us/library/dd723653(v=vs.113).aspx
Regards
Kajal
There are alternate data annotations when creating your own data model for OData.
See the System.ComponentModel.DataAnnotations and (depending on the framework version) System.ComponentModel.DataAnnotations.Schema. However, I've used these when creating my WCF DataService using DbContext, not DataServiceContext for the underlying model.
I have an entity and a corresponding DTO
public class PersonEntity {
public int personId;
public List<Contact> contacts;
}
public class PersonDto {
public int personId;
public List<int> contacts;
}
Using the following map with AutoMapper
Mapper.Map<PersonDto, Person>();
I'm using AutoMapper to get the DTO, which isn't a problem.
I'm parsing the DTO back to the Entity, to update fields in the Entity for a save operation and I'm not really interested in the list of contacts anymore. Automapper throws an exception with this as it doesn't like mapping the list of int's to a list of objects.
any suggestions or better ways to do this please.
Edit
solution used is
Mapper.CreateMap<PersonDto, Person>()
.ForMember(x => x.contacts, y => y.Ignore());
Can you use the ignore method in configuration?
http://automapper.codeplex.com/wikipage?title=Configuration%20Validation
opt => opt.Ignore()
But. Do you really need to update the entity just to save? Why don't you send a command which contains the changed data.
AutoMapper as you may know, looks for properties in objects with the same name & type, therefore you will most likely need to alter your DTO or Entity so they match.