I am working with a ASP.NET Core WebAPI and I want to do CRUD for my objects called "Item".
I am using EF Core to work with a SQL Database and I have two models that represents my objects.
ItemDto - Data Transfer Object for an Item
ItemEntity - Database Object (represents a row in a table 1:1)
My HTTP GET one and HTTP GET many methods works in such way that it
Get ItemRepository instance
Fetch one or more ItemEntity
Map it to ItemDto by using AutoMapper
This is initalized in my constructor such as
m_itemDtoMapper = new Mapper(new MapperConfiguration(cfg => cfg.CreateMap<ItemEntity, ItemDto>()));
And in my WebAPI method I map it to a ItemDto with the following line (for the GET many case):
var itemDtos = m_itemDtoMapper.Map<IEnumerable<ItemEntity>, ICollection<ItemDto>>(items);
This works well and the AutoMapper is very powerful. The questions I have now is:
Is this the standard way of managing the relationship between database entities and data transfer objects?
In the CreateItem method, I need to do reverse mapping. Instead of mapping ItemEntity to ItemDto, I need to map a ItemDto to an ItemEntity. How shall I do this? Creating a copy of my mapper just with switched entities works but is that how its supposed to be done? i.e two mappers.
In your example, it seems that you initialize each time a new mapper instance. I would suggest you go along with dependency injection and make use of AutoMapper Mapping Profiles.
You can do it in three simple steps and I think it answers both of your questions:
Step 1:
Simply create a new class called MappingProfile or something similar:
public class MappingProfile: Profile
{
public MappingProfile()
{
CreateMap<User, AuthenticateDto>(); // One Way
CreateMap<User, UserDto>().ReverseMap(); // Reverse
}
}
Step 2: Register Automapper in Startup.cs
// Register AutoMapper
services.AddAutoMapper(Assembly.GetExecutingAssembly());
Step 3: Consume your mapper over DI
public UserService(IMapper mapper) {
_mapper = mapper;
}
// call it as you already did
_mapper.Map<User, UserDto>(user);
Hopefully it helps you :)
Related
How do I even use AutoMapper correctly in a layered architecture and where do I initialize it if my path described below is not correct?
There is a solution screenshot
for the sake of a common understanding :)
As you can see, there are DTO's, Models and ViewModels in different layers
I want a mapping from DAL to BLL and then to (console)PL and conversely
But I get
AutoMapper.AutoMapperMappingException: 'Missing type map configuration or unsupported mapping'
can't find any similar examples for me
Thanks in advance
Here is an example of how I connect AutoMapper for ViewModel->DTO mapping, maybe this is not the right way?
I have the same way AutoMapper is connected in BLL for mapping DTO->Model
private IMapper _mapper;
private readonly BLL.BookService _bookService;
public UiBookService(UserDTO currentUser)
{
_mapper = new MapperConfiguration(cfg => cfg.CreateMap<BookViewModel, BookDTO>()).CreateMapper();
_bookService = new BLL.BookService(currentUser);
CurrentUser = currentUser;
}
then use it like this
await _bookService.CreateBook(_mapper.Map<BookDTO>(book));
BLL.BookService: (This is where this error occurs)
public async Task CreateBook(BookDTO book)
{
book.OwnerId = _currentUser.Id;
await _bookRepo.CreateAsync(_mapper.Map<Book>(book));
}
In this UiBookService, we create a BookViewModel from the console and pass it as BookDTO to BookService from BLL.
The strange thing is that in DTO the mapping goes fine, but in BLL it doesn't map from DTO to a normal Model
I would create two MappingConfig files.
Model <-> DTO in BLL.Mapping
DTO <-> ViewModel in PL.Mapping
This will allow you to test your mappings and use one mapping for the same signature among the whole program. Also, make sure that all mappings between entities are configured.
After that, inject them in a DI container.
I am maintaining an application which uses EF Core to persist data to a SQL database.
I am trying to implement a new feature which requires me to retrieve an object from the database (Lets pretend its an order) manipulate it and some of the order lines which are attached to it and save it back into the database. Which wouldn't be a problem but I have inherited some of this code so need to try to stick to the existing way of doing things.
The basic process for data access is :
UI -> API -> Service -> Repository -> DataContext
The methods in the repo follow this pattern (Though I have simplified it for the purposes of this question)
public Order GetOrder(int id)
{
return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}
The service is where business logic and mapping to DTOs are applied, this is what the GetOrder method would look like :
public OrderDTO GetOrder(int id)
{
var ord = _repo.GetOrder(id);
return _mapper.Map<OrderDto>(ord);
}
So to retrieve and manipulate an order my code would look something like this
public void ManipulateAnOrder()
{
// Get the order DTO from the service
var order = _service.GetOrder(3);
// Manipulate the order
order.UpdatedBy = "Daneel Olivaw";
order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");
_service.SaveOrder(order);
}
And the method in the service which allows this to be saved back to the DB would look something like this:
public void SaveOrder(OrderDTO order)
{
// Get the original item from the database
var original = _repo.GetOrder(order.Id);
// Merge the original and the new DTO together
_mapper.Map(order, original);
_repo.Save(original);
}
Finally the repositories save method looks like this
public void Save(Order order){
_context.Update(order)
_context.SaveChanges();
}
The problem that I am encountering is using this method of mapping the Entities from the context into DTOs and back again causes the nested objects (in this instance the OrderLines) to be changed (or recreated) by AutoMapper in such a way that EF no longer recognises them as being the entities that it has just given to us.
This results in errors when updating along the lines of
InvalidOperationException the instance of ProductLine cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
Now to me, its not that there is ANOTHER instance of the object being tracked, its the same one, but I understand that the mapping process has broken that link and EF can no longer determine that they are the same object.
So, I have been looking for ways to rectify this, There are two ways that have jumped out at me as being promising,
the answer mentioned here EF & Automapper. Update nested collections
Automapper.Collection
Automapper.collection seems to be the better route, but I cant find a good working example of it in use, and the implementation that I have done doesn't seem to work.
So, I'm looking for advice from anyone who has either used automapper collections before successfully or anyone that has any suggestions as to how best to approach this.
Edit, I have knocked up a quick console app as an example, Note that when I say quick I mean... Horrible there is no DI or anything like that, I have done away with the repositories and services to keep it simple.
I have also left in a commented out mapper profile which does work, but isn't ideal.. You will see what I mean when you look at it.
Repo is here https://github.com/DavidDBD/AutomapperExample
Ok, after examining every scenario and counting on the fact that i did what you're trying to do in my previous project and it worked out of the box.
Updating your EntityFramework Core nuget packages to the latest stable version (3.1.8) solved the issue without modifying your code.
AutoMapper in fact "has broken that link" and the mapped entities you are trying to save are a set of new objects, not previously tracked by your DbContext. If the mapped entities were the same objects, you wouldn't have get this error.
In fact, it has nothing to do with AutoMapper and the mapping process, but how the DbContext is being used and how the entity states are being managed.
In your ManipulateAnOrder method after getting the mapped entities -
var order = _service.GetOrder(3);
your DbContext instance is still alive and at the repository layer it is tracking the entities you just retrieved, while you are modifying the mapped entities -
order.UpdatedBy = "Daneel Olivaw";
order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");
Then, when you are trying to save the modified entities -
_service.SaveOrder(order);
this mapped entities reach the repository layer and DbContext tries to add them to its tracking list, but finds that it already has entities of same type with same Ids in the list (the previously fetched ones). EF can track only one instance of a specific type with a specific key. Hence, the complaining message.
One way to solve this, is when fetching the Order, tell EF not to track it, like at your repository layer -
public Order GetOrder(int id, bool tracking = true) // optional parameter
{
if(!tracking)
{
return _context.Orders.Include(o=>o.OrderLines).AsNoTracking().FirstOrDefault(x=>x.Id == id);
}
return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}
(or you can add a separate method for handling NoTracking calls) and then at your Service layer -
var order = _repo.GetOrder(id, false); // for this operation tracking is false
In a project using ASP.NET Core 2.0 and Entity Framework, I'm trying to map a known table schema (coded into class MyTableClass) to an unknown table name. This table name is given by the user at run time, so this is done outside of the OnModelCreating method of the Context class. Is there a way to do something like the following pseudocode:
void OnUserEnteredTableNameFromUI(string tableName)
{
var modelBuilder = new ModelBuilder(???); // how?
modelBuilder.Entity<MyTableClass>().ToTable(tableName);
// how to get a ref to DbSet<MyTableClass> myTable from here?
}
Since this is an interesting issue which might help other people that need some dynamic model building, here is how it can be implemented.
Let say we have a custom context with custom table name provided via constructor (as Gert Arnold suggested in the other answer):
public class CustomDbContext : DbContext
{
// …
private string customTableName;
public string CustomTableName => customTableName ?? "DefaultCustomTableName";
}
and we use it inside the OnModelCreating (it should be there, currently there is no other simple way to create model using the predefined convention sets):
modelBuilder.Entity<CustomEntity>().ToTable(CustomTableName);
The only problem is that by default the OnModelCreating is called just once per context type and is cached. Luckily EF Core is built on top of a (replaceable) services architecture. The service interface responsible for model caching is IModelCacheKeyFactory:
Creates keys that uniquely identifies the model for a given context. This is used to store and lookup a cached model for a given context.
It has a single method
object Create(DbContext context)
The returned object GetHashCode / Equals methods are used to identify the passed context instance. The default EF Core service implementation returns an object which compares the type of the context.
In order to make the custom context model working, we need to replace it with a custom service which also compares the custom state (CustomTableName in our case). The implementation could be like this (using C#7.0 value tuples):
class CustomModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context) => new CustomModelCacheKey(context);
}
class CustomModelCacheKey
{
(Type ContextType, string CustomTableName) key;
public CustomModelCacheKey(DbContext context)
{
key.ContextType = context.GetType();
key.CustomTableName = (context as CustomDbContext)?.CustomTableName;
}
public override int GetHashCode() => key.GetHashCode();
public override bool Equals(object obj) => obj is CustomModelCacheKey other && key.Equals(other.key);
}
The only thing remaining is to replace the existing service with the custom. It can be done inside OnConfiguring override:
optionsBuilder.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory>();
And that's all. Anytime you create context with different CustomTableName, EF Core will create a new model and map the CustomEntity to that table.
The same technique can be applied to any context containing custom model affecting state by including all custom state in CustomModelCacheKey.key tuple. Of course it could be implemented w/o value tuples, just with them the GetHashCode and Equals overrides are easier to implement. Actually instead of CustomModelCacheKey the custom service can return directly value tuple containing the context type and custom state member values.
I've seen situations where databases with identical structure but varying table names had been deployed to several sites. In that case, EF only needs the know the table name(s) at application startup.
This can be done by adding a constructor parameter to the context:
private readonly string _userDefinedTableName;
public MyContext(string userDefinedTableName)
{
_userDefinedTableName = userDefinedTableName;
}
Then, in OnModelCreating:
modelBuilder.Entity<MyTableClass>().ToTable(_userDefinedTableName);
However, in your case the name has to change any number of times at runtime. With Entity Framework, that's impossible (well, more exactly, too impractical to really contemplate it). EF compiles and stores model once per context class, because it would be too expensive to do all that for each context instantiation.
That means that OnModelCreating runs not more than once in an application and the first table name remains.
You'll have to find other ways to address table data dynamically, or change the design so the multiple tables can be converted into one fixed table.
I am trying to use AutoMapper 6.3 to allow me to auto map my model into viewmodels.
First I registered my AutoMapper instance to my IUnitContainer like so
var mapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
container.RegisterInstance<IMapper>(mapper.CreateMapper());
Now, in my controller, I want to pull a model from the database, then I want to map/cast it to my view-model.
I tried to do the following
var task = UnitOfWork.Tasks.Get(123)
.ProjectTo<TaskViewModel>();
But I can't seems to find the ProjectTo extension which I assumed it will be part of the AutoMapper project.
What is the correct way to project the viewModel if my AutoMapperProfile already created the mapping?
It seems to be in Automapper.QueryableExtensions:
https://github.com/AutoMapper/AutoMapper/blob/8dd104aa7390c12c97c4195cce6f6ff66de24f51/src/AutoMapper/QueryableExtensions/Extensions.cs
you can call it as long as the previous item in your linq change is IQueryable it seems.
BACKGROUND:
Our core framework loads all entity framework mappings from itself, the main application and any modules we have installed by using an interface (below):
public interface IEntityTypeConfiguration : IDependency
{
}
and we have a DbContext in our core framework like this, which loads all the mappings:
public class DefaultDbContext : DbContextBase
{
private readonly Lazy<IEnumerable<IEntityTypeConfiguration>> configurations;
public DefaultDbContext(Lazy<IEnumerable<IEntityTypeConfiguration>> configurations)
: base()
{
this.configurations = configurations;
Configuration.ProxyCreationEnabled = false;
}
public DefaultDbContext(string connectionString, Lazy<IEnumerable<IEntityTypeConfiguration>> configurations)
: base(connectionString)
{
this.configurations = configurations;
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
foreach (dynamic typeConfiguration in configurations.Value)
{
modelBuilder.Configurations.Add(typeConfiguration);
}
Database.SetInitializer(new CreateTablesIfNotExist<DefaultDbContext>());
}
}
So this way we have a single DbContext for everything.
PROBLEM:
We've run into an issue whereby when we dynamically add new modules (which have their own mappings), then EF does not load those mappings ever, even when we are sure that a new instance of DefaultDbContext has been created. So, it must be that EF is cacheing the mappings somewhere. Is there some way to clear the cache?
FINAL NOTE:
As you may have guessed, we are using an IoC, namely Autofac. If you need any further info, just ask.
Any ideas, anyone?
The model is cached for performance reasons.
The following excerpt explains what is going on
Model Caching
There is some cost involved in discovering the model, processing Data Annotations and applying fluent API configuration. To avoid incurring this cost every time a derived DbContext is instantiated the model is cached during the first initialization. The cached model is then re-used each time the same derived context is constructed in the same AppDomain.
This text also mentions a property called CacheForContextType but this didn't make it into the final release of EF5.
This second link provides a glimmer of hope but again is dated before the final release of EF5
We removed CacheForContextType in CTP5, we originally intended it to be used when folks wanted to use the same context in the same AppDomain with different models. The issue is that it would create the model on every initialization and didn't allow any way to cache a series of models and choose which one to use during each initialization. Model creation is expensive so we wanted to promote a better pattern.
The pattern we recommend is to externally create a ModelBuilder -> DbDatabaseMapping -> DbModel for each model you want to use. The DbModel should be cached and used to create context instances. The ModelBuilder -> DbModel workflow is a little messy and the class names aren't great, they will be tidied up for RTM.
Personally I think you're going to have to find a way of knowing all of your models up front ...
Solved! We found this constructor on the DbContext class:
public DbContext(string nameOrConnectionString, DbCompiledModel model);
I can't share all of the code here, but basically we're creating a new DbCompiledModel and passing that in whenever necessary.