I have this situation:
// Core Business classes
public class Invoice
{
public Guid Id { get; protected set; }
public int Number { get; set; }
public IList<InvoiceItem> Items { get; protected set; }
}
public class InvoiceItem
{
public Guid Id { get; protected set; }
public string Product { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
// MVC Models
public class InvoiceModel
{
public Guid Id { get; set; }
public int Number { get; set; }
public IList<InvoiceItemModel> Items { get; set; }
}
public class InvoiceItemModel
{
public Guid Id { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
The automapper configuration
public class MyProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Invoice, InvoiceModel>();
Mapper.CreateMap<InvoiceItem, InvoiceItemModel>();
}
}
Then when I want to pass a model to my view, for example to edit an Invoice object, I do:
...
var invoice = Repository.Get<Invoice>(id);
return View("Update", Mapper.Map<InvoiceModel>(invoice));
...
And then I can iterate the Items collection with InvoiceItemModels.
The issue is when I want to retrieve a bunch of Invoices, for example in an index.
...
var invoices = Repository.ListAll<Invoice>();
return View("Index", invoices.Select(Mapper.Map<InvoiceModel>).ToList());
...
I don't want the "Items" to be loaded. A better configuration for this case will be:
public class MyFlatProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Invoice, InvoiceModel>()
.ForMember(m => m.Items, opt => opt.Ignore());
Mapper.CreateMap<InvoiceItem, InvoiceItemModel>();
}
}
But I have no idea how to switch between "Profiles".
Is there a way to "pick" a particular configuration of mapping?
Unfortunately, you have to create separate Configuration objects, and create a separate MappingEngine for each.
First, declear a static class to hold the mappers
public static class MapperFactory
{
public static MappingEngine NormalMapper()
{
var normalConfig = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
normalConfig.CreateMap<Invoice, InvoiceModel>();
normalConfig.CreateMap<InvoiceItem, InvoiceItemModel>();
var normalMapper = new MappingEngine(normalConfig);
return normalMapper;
}
public static MappingEngine FlatMapper()
{
var flatConfig = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
flatConfig.CreateMap<Invoice, InvoiceModel>()
.ForMember(m => m.Items, opt => opt.Ignore());
flatConfig.CreateMap<InvoiceItem, InvoiceItemModel>();
var flatMapper = new MappingEngine(flatConfig);
return flatMapper;
}
}
Then you can call the MappingEngine to do the mapping (The syntax is the same as Mapper object).
return View("Update", MapperFactory.FlatMapper().Map<InvoiceModel>(invoice));
return View("Update", MapperFactory.NormalMapper().Map<InvoiceModel>(invoice));
Related
Models:
namespace practise_API.Model
{
public class SourceAttributes
{
[Key]
public int SourceEntityId { get; set; }
public int ATiD { get; set; }
public string Name { get; set; }
public string Datatype { get; set; }
}
public class OtherData
{
public int ID { get; set; }
public string Ename { get; set; }
public string Type { get; set; }
public string Source { get; set; }
public string Frequency { get; set; }
}
}
IRepository interface:
namespace API.Repository
{
public interface ISourceRepository
{
IEnumerable<SourceEntities> GetSourceEntities();
IEnumerable<SourceAttributes> GetSourceAttributes(int id);
........
void Save();
}
}
Repository implementation:
public class SourceRepository : ISourceRepository
{
private readonly MapperDbContext _dbContext;
public SourceRepository(MapperDbContext dbContext)
{
_dbContext = dbContext;
}
public IEnumerable<SourceAttributes> GetSourceAttributes()
{
return _dbContext.SourceAttributes
.Where(i => i.SourceEntityId == OtherData.ID)
.ToList();
}
}
I want to get all the SourceAttributes.SourceentityIds that match that one ID from OtherData. The way I thought about it was to use where() System.Linq but I can't get the OtherData.ID.
Also this is going to be called later in my Get() of my controller.
The ISourceRepository.GetSourceAttributes has an id parameter, but the implementation is missing it resulting surely in a compiler error.
Modify it:
public IEnumerable<SourceAttributes> GetSourceAttributes(int otherDataId)
{
return _dbContext.SourceAttributes
.Where(i => i.SourceEntityId == otherDataId)
.ToList();
}
To get the ids you want:
var ids = sourceRepository
.GetSourceAttributes(myOtherDataInstance.ID)
.Select(i => i.ID)
.ToArray(); // or leave it as IEnumerable as you wish
I trying to use a identity class inside of my domain object but when i want to create migration for create database the ef core 2.2 say me:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: 'Warehouse' cannot be used as a property on entity type 'Existence' because it is configured as a navigation.
my dbcontext is
public class WarehousesContext : BaseContext<WarehousesContext>
{
public WarehousesContext(DbContextOptions<WarehousesContext> options) : base(options)
{
}
public WarehousesContext() : base() { }
public DbSet<Warehouse> Warehouses { get; set; }
public DbSet<Existence> Existences { get; set; }
public DbSet<Entry> Entries { get; set; }
public DbSet<Exit> Exits { get; set; }
public DbSet<Transfer> Transfers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Inventory");
modelBuilder.Entity<Warehouse>().ToTable("Warehouses");
modelBuilder.Entity<Warehouse>().HasKey(w => w.Id);
modelBuilder.Entity<Warehouse>().Property(w => w.Id).HasConversion(v => v.Id, v => new WarehouseId(v));
modelBuilder.Entity<Existence>().ToTable("Existences");
modelBuilder.Entity<Existence>().HasKey(e => e.Id);
modelBuilder.Entity<Existence>().Property(e => e.Id).HasConversion(v => v.Id, v => new ExistenceId(v));
modelBuilder.Entity<Existence>().OwnsOne(e => e.Warehouse);
modelBuilder.Entity<Existence>().OwnsOne(e => e.Product);
}
}
my existences class is
public class Existence
{
public ExistenceId Id { get; private set; }
public WarehouseId Warehouse { get; private set; }
public ProductId Product { get; private set; }
public decimal Quantity { get; private set; }
public string Batch { get; private set; }
private Existence() { }
public Existence(WarehouseId warehouse, ProductId product, decimal quantity, string batch)
{
Warehouse = warehouse;
Product = product;
Quantity = quantity;
Batch = batch;
}
internal void Add(decimal quantity)
{
Quantity += quantity;
}
internal void Subtract(decimal quantity)
{
Quantity -= quantity;
if (Quantity < 0)
throw new Exception();
}
and my warehouseId class
public class WarehouseId
{
public string Id { get; private set; }
public WarehouseId()
{
this.Id = Guid.NewGuid().ToString();
}
public WarehouseId(string id)
{
Id = id;
}
}
i think the problem are that i use the "entityId" pattern to name my identity class so i want to know if existe some way to tell ef core "don't try to use navigation property convection here"
Change your Existence class as below(you can add your methods accordingly)
public class Existence
{
public string Id { get; private set; }
[ForeignKey("Warehouse")]
public string WarehouseId { get; private set; }
public ProductId Product { get; private set; }
public decimal Quantity { get; private set; }
public string Batch { get; private set; }
public virtual Warehouse Warehouse{get;set;)
}
public class Warehouse
{
//your other Warehouse properties
//add below line, if one to one relation
public virtual Existence Existence{get; set;}
//or, add below line, if one to many relation
//public virtual IList<Existence> Existence{get; set;}
}
Remove below lines from OnModelCreating method,
modelBuilder.Entity<Existence>().OwnsOne(e => e.Warehouse);
modelBuilder.Entity<Existence>().OwnsOne(e => e.Product);
and you can refer below SO question to correct your identity generation.
How does Entity Framework generate a GUID for a primary key value?
How to correctly handle computed properties in EF model?
My try bellow will fail because of "The entity or complex type 'Invoice' cannot be constructed in a LINQ to Entities query."
Consider method "GetInvoice" as WebApi method with allowed querystring.
static void Main(string[] args)
{
var invs = GetInvoice();
invs.FirstOrDefault();
}
public static IQueryable<Invoice> GetInvoice()
{
var model = new Model();
IQueryable<Invoice> inv = model.Invocies.Include(t => t.Items).SelectInvoiceData();
return inv;
}
public static class ExtHelper
{
public static IQueryable<Invoice> SelectInvoiceData(this IQueryable<Invoice> item)
{
return item.Select(c => new Invoice
{
LatestItemName = c.Items.FirstOrDefault().Name
});
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public string Issuer { get; set; }
[NotMapped]
public string LatestItemName { get; set; }
private ICollection<Item> _items;
public virtual ICollection<Item> Items
{
get { return _items ?? (_items = new Collection<Item>()); }
set { _items = value; }
}
}
EntityFramework 6 does not support creating partial entities like this. Either use anonymous type:
return item.Select(c => new
{
LatestItemName = c.Items.FirstOrDefault().Name
});
Or some DTO class that does not belong to context:
return item.Select(c => new InvoiceDTO
{
LatestItemName = c.Items.FirstOrDefault().Name
});
However in EF Core it is possible to create entities like in your example.
I have a problem with many to many relationship in EF core.
I have the following model classes:
public class Meal
{
public int Id { get; set; }
[Required]
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Meal()
{
MealFoods = new Collection<MealFood>();
}
}
public class Food
{
public int Id { get; set; }
[StringLength(255)]
public string Name { get; set; }
[Required]
public int Carbohydrates { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Food()
{
MealFoods = new Collection<MealFood>();
}
}
public class MealFood
{
public int MealId { get; set; }
public Meal Meal { get; set; }
public int FoodId { get; set; }
public Food Food { get; set; }
}
I have the following API resource class:
public class MealResource
{
public int Id { get; set; }
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<FoodResource> Foods { get; set; }
public MealResource()
{
Foods = new Collection<FoodResource>();
}
}
I have done the mapping in my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MealFood>().HasKey(mf => new { mf.MealId, mf.FoodId });
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Meal).WithMany(m => m.MealFoods).HasForeignKey(mf => mf.MealId);
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Food).WithMany(f => f.MealFoods).HasForeignKey(mf => mf.FoodId);
}
I've got a problem with this call:
var meals = await context.Meals.Include(m => m.MealFoods).ToListAsync();
This returns almost everything I need, except the navigation properties from MealFoods
The reason why I want those properties, because I want to do the following mapping:
CreateMap<Meal, MealResource>().ForMember(mr => mr.Foods, opt => opt.MapFrom(x => x.MealFoods.Select(y => y.Food).ToList()));
I have already found this:
Automapper many to many mapping
but (maybe I don't get something) this doesn't work because the property called Food in MealFood is null.
I hope I didn't explain too complex.
When you include navigation property, EF Core automatically fills the inverse navigation property, e.g. including Meal.MealFoods will automatically fill MealFood.Meal, including Food.MealFoods will automatically populate MealFood.Food etc. In order to populate other navigation properties you need to use additional ThenInclude. E.g.
var meals = await context.Meals
.Include(m => m.MealFoods)
.ThenInclude(mf => mf.Food) // <--
.ToListAsync();
or
var foods = await context.Foods
.Include(f => f.MealFoods)
.ThenInclude(mf => mf.Meal) // <--
.ToListAsync();
I want to map source class to derived (from abstract) destination classes depend on value of some property.
I have the following source classes:
public partial class ApplicationDriver
{
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public string Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
I want to map to the following classes, depend on Type parameter:
public class ApplicationDriverDomain
{
public List<ApplicationDriverEquipmentAbstractDomain> Equipments { get; set; }
}
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
I try to do it:
ApplicationDriverEquipmentAbstractDomain GetEquipment(Infrastructure.Asset.ApplicationDriverEquipment infrastructure)
{
ApplicationDriverEquipmentAbstractDomain result = null;
var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperApplicationModel>());
var mapper = config.CreateMapper();
switch (infrastructure.Type)
{
case ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor:
result = mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer:
result = mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck:
result = mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan:
result = mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
break;
}
return result;
}
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentAbstractDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>()
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(GetEquipment))
;
CreateMap<Infrastructure.Asset.ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.MapFrom(src => src.Equipments));
but I got an error:
"Error mapping types.\r\n\r\nMapping types:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nType Map
configuration:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nProperty:\r\nEquipments"
Updated:
So I believe I understand what you are trying to do, and apologies I may have slightly led you down the incorrect route. You flow is basically to distinguish what infrastructure type the source object is and then create that type of object. Also you need to understand the two different Mapper set up ways.
In the first part of your code you are trying to set it up with an instance of the Mapper but then using my Static style of using the Mapper.Map I would recommend always using the static style so that you have the ability to do some more dynamic ways of pulling mapping profiles in.
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var domain = Mapper.Map<Domain.ApplicationDriverEquipmentTractorDomain>(inf);
Next you only need to reference that a mapping type from the underlying source to the domain types in your profile i.e.
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTractorDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentCargoVanDomain>();
Then what you need to do is to call your GetEquipment method from the mapping that describes the ApplicationDriver i.e.
CreateMap<ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.ResolveUsing(x => x.Equipments.Select(GetEquipment)));
private ApplicationDriverEquipmentAbstractDomain GetEquipment(ApplicationDriverEquipmentInfrastructure infrastructure)
{
switch (infrastructure.Type)
{
case "Tractor":
return Mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
case "Trailer":
return Mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
case "StraightTruck":
return Mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
case "CargoVan":
return Mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
}
return null;
}
Example Usage:
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var inf = new ApplicationDriverEquipmentInfrastructure()
{
CurrentMileage = "mil",
Length = "123",
Make = "ccc",
Model = "15",
Type = "Tractor",
VINNumber = "vin"
};
var driver = new ApplicationDriver()
{
Equipments = new List<ApplicationDriverEquipmentInfrastructure>() {inf}
};
var domain = Mapper.Map<ApplicationDriverDomain>(driver);
Inheritance in AM works by checking the type of the source, not by using a discriminator. That's what you were supposed to understand from the docs. One way to solve your problem is to pass an existing destination to Map. Created by smth like the GetEquipment method you have there. ApplyBaseMapping is a hack, you use Include/IncludeBase to reuse configuration. Unfortunately you've also hit a bug already fixed in the MyGet build, so the real error was kind of hidden from you. The only way to debug this in your version is by checking the execution plan.