I have an application I haven't touched for a while and it is giving me some grief.
When I call the index method of the controller I get the following error:
I am not sure what I am doing wrong but it would seem that AutoMapper is having trouble mapping a collection of Shift objects to a ShiftViewModel.
I have included some snippets below.
Thoughts?
My controller:
using AutoMapper;
using My.DataAccess;
using My.Entity.DatabaseEntities;
using My.Entity.ViewModels;
using My.Service;
using System.Net;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace My.Controllers
{
public class ShiftController : Controller
{
//initialize service object
readonly IShiftService _shiftService;
private readonly IMapper _mapper;
public ShiftController(IShiftService shiftService, IMapper mapper)
{
_shiftService = shiftService;
_mapper = mapper;
}
readonly ApplicationDataManager db = new ApplicationDataManager();
// GET: /Shifts/
public ActionResult Index()
{
var shifts = _shiftService.GetAll();
if (shifts == null)
{
return HttpNotFound();
}
var model = _mapper.Map<ShiftViewModel>(shifts);
return View(model);
}
}
}
Shift database entity:
using System;
using System.ComponentModel.DataAnnotations;
namespace My.Entity.DatabaseEntities
{
public class Shift : AuditableEntity<long>
{
[Required, StringLength(6)]
[Editable(true)]
public string Code { get; set; }
public string Detail { get; set; }
public DateTime Start { get; set; }
public long Duration { get; set; }
}
}
ShiftViewModel class:
using System;
using System.ComponentModel.DataAnnotations;
namespace My.Entity.ViewModels
{
public class ShiftViewModel
{
public int Id { get; set; }
public string Code { get; set; }
public string Detail { get; set; }
public DateTime Start { get; set; }
[Display(Name = "Duration of shift")]
[DisplayFormat(DataFormatString = "{0:HH:mm}", ApplyFormatInEditMode = true)]
[DataType(DataType.Duration)]
public string DurationTime
{
get
{
var ts = new TimeSpan(Duration);
var h = ts.Hours == 1 ? "hour" : "hours";
var m = ts.Minutes == 1 ? "min" : "mins";
return string.Format("{0} {1} {2} {3}", ts.Hours, h, ts.Minutes, m);
}
}
public long Duration { get; set; }
}
}
Global.asax:
using Autofac;
using Autofac.Integration.Mvc;
using My.DataAccess.Modules;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace My.App
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//Autofac Configuration
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();
builder.RegisterModule(new RepositoryModule());
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EFModule());
//Register AutoMapper here using AutoFacModule class (Both methods works)
//builder.RegisterModule(new AutoMapperModule());
builder.RegisterModule<AutoFacModule>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
}
AutoFacModule:
using Autofac;
using AutoFacAndAutoMapperMVC.Infrastructure;
using AutoMapper;
public class AutoFacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(context => new MapperConfiguration(cfg =>
{
//Register Mapper Profile
cfg.AddProfile<AutoMapperProfile>();
}
)).AsSelf().InstancePerRequest();
builder.Register(c =>
{
//This resolves a new context that can be used later.
var context = c.Resolve<IComponentContext>();
var config = context.Resolve<MapperConfiguration>();
return config.CreateMapper(context.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();
}
}
AutoMapperProfile:
using Roster.Entity.DatabaseEntities;
using Roster.Entity.ViewModels;
using AutoMapper;
namespace AutoFacAndAutoMapperMVC.Infrastructure
{
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Shift, ShiftViewModel>();
CreateMap<ShiftViewModel, Shift>();
}
}
}
Trekco, It is called by a generic method of IEnumerable
public virtual IEnumerable<T> GetAll()
{
return _repository.GetAll();
}
it returns a Shift object
In you Index method, the code -
var shifts = _shiftService.GetAll();
is definitely not returning a single Shift object. I guess, its returning a list/collection of Shift object. If so, then with the code -
var model = _mapper.Map<ShiftViewModel>(shifts);
you are trying to map a list of Shift object to a single ShiftViewModel object which is causing the issue.
Change the mapping code to -
var model = _mapper.Map<List<ShiftViewModel>>(shifts);
So this is how I solved the problem. Thanks everyone for the help.
Solution
So it seems, as Akos Nagy, points out in his blog post the problem is that AutoMapper and Autofac don't play very well together. This wasn't helped by the fact that my code was not very good to begin with.
The AutoMapper docs had a page on Dependency Injection found here. There were no real examples for AutoFac however it did point me towards a Nuget package named AutoMapper.Contrib.Autofac.DependencyInjection 5.2.0. There is a GitHub project here.
So I installed that package with the Package Manager Console.
Install-Package AutoMapper.Contrib.Autofac.DependencyInjection -Version 5.2.0
I then simplified my Shift domain object class.
using System;
using System.ComponentModel.DataAnnotations;
namespace Roster.Entity.DatabaseEntities
{
public class Shift : AuditableEntity<long>
{
#region Public Properties
[Required, StringLength(6)]
[Editable(true)]
[Display(Name = "Code")]
public string Code { get; set; }
public string Detail { get; set; }
public DateTime Start { get; set; }
public long Duration { get; set; }
#endregion Public Properties
}
}
Next I reworked my ViewModel, again just to make things a bit cleaner and adding a little business logic functionality.
using System;
using System.ComponentModel.DataAnnotations;
namespace Roster.Entity.ViewModels
{
public class ShiftViewModel : AuditableEntity<long>
{
[Required, StringLength(6)]
[Editable(true)]
[Display(Name = "Code")]
public string Code { get; set; }
public string Detail { get; set; }
[DisplayFormat(DataFormatString = "{0:HH:mm }", ApplyFormatInEditMode = true)]
public DateTime Start { get; set; }
public long Duration { get; set; }
[Display(Name = "Text Duration time of shift")]
[DisplayFormat(DataFormatString = "{0:hh:mm}", ApplyFormatInEditMode = true)]
[DataType(DataType.Duration)]
public string DurationTime
{
get
{
TimeSpan ts = new TimeSpan(Duration);
var h = ts.Hours == 1 ? "hour" : "hours";
var m = ts.Minutes == 1 ? "min" : "mins";
return string.Format("{0} {1} {2} {3}", ts.Hours, h, ts.Minutes, m);
}
}
[Display(Name = "Shift Duration")]
[DisplayFormat(DataFormatString = "{0:hh\\:mm}", ApplyFormatInEditMode = true)]
[DataType(DataType.Duration)]
public string ShiftDuration
{
get
{
return TimeSpan.FromTicks(Duration).ToString();
}
set
{
TimeSpan interval = TimeSpan.FromTicks(Duration);
Duration = interval.Ticks;
}
}
}
}
Now on to mapping the domain object to my ViewModel.
First I needed to create an AutoMapper profile to create the map. I saved this in the APP_Start folder for no reason than I thought it a good place.
using AutoMapper;
using Roster.Entity.DatabaseEntities;
using Roster.Entity.ViewModels;
namespace AutoFacAndAutoMapperMVC.Infrastructure
{
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Shift, ShiftViewModel>()
.ReverseMap();
}
}
}
Next I needed to change Global.asax.cs
I registered AutoMapper by adding
builder.RegisterAutoMapper(typeof(MvcApplication).Assembly);
I then added the following lines to resolve the mapper service and control the lifetime scope. I am not sure what this actually does but it is recommended in the AutoFac docs here
using(var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<IMapper>();
}
Finally I altered the Shift Controller to use AutoMapper.
using AutoMapper;
using Roster.DataAccess;
using Roster.Entity.ViewModels;
using Roster.Service;
using System.Collections;
using System.Collections.Generic;
using System.Web.Mvc;
namespace Roster.Controllers
{
public class ShiftController : Controller
{
//initialize service object
private readonly IShiftService _shiftService;
private readonly IMapper _mapper;
public ShiftController(IShiftService shiftService, IMapper mapper)
{
_shiftService = shiftService;
_mapper = mapper;
}
readonly ApplicationDataManager db = new ApplicationDataManager();
// GET: /Shifts/
public ActionResult Index()
{
IEnumerable shifts = _shiftService.GetAll();
var model = _mapper.Map<IEnumerable<ShiftViewModel>>(shifts);
if (model == null)
{
return HttpNotFound();
}
return View(model);
}
}
}
Importantly and a real beginners error on my behalf is that because I had a collection of shifts to map I had to have a collection of viewmodels. Thanks atiyar. I resolved this by mapping like this. So stupid on my behalf.
var model = _mapper.Map<IEnumerable<ShiftViewModel>>(shifts);
So sorry about the long answer but I thought I would wrap up the question with how I resolved my problem. It was a great learning exercise for someone who is not a professional programmer. Thanks everyone.
Related
The overall goal is to be able to perform a lookup of a meal based on the userid, and mealid. If that item is found, I want to be able to delete only the entire element that matches both the userid and mealid.
Currently, After sending the request object via postman in a post request, I have tried to write a variation of queries using the C# to create a builder object, and then filter to find the specific array object, and finally delete the array object if there is a match on both userid and mealid. Initially, I was getting the issue where the entire element was not being deleted, but only the interal array element that is nested inside of the element was being (not deleted, but set back to null values). However, now the issue is that the entire array element is not being deleted at all and i'm getting the following error.
BsonArraySerializer Error from Visual Studio
Can someone please help me to resolve this issue?
Here is a sample object that I'm sending via postman that I'm trying to delete:
Sample Postman POST request with Body data
Here is an example image of data that I'm trying to delete:
Sample Image of Json Array Elemet I'm trying to delete
You will need MongoDb Compass or Atlas, .NET Core 3.1, MongoDB C# Driver 2.0, Postman, and .NET Core 3.1 WebApi Project/Solution in order to help solve this issue.
Below, is the code that is needed to replicate the issue:
Startup.cs
Add this line of code to the Configuration method of this file
services.AddScoped<IMealsRepository, MealsRepository>();
Add this line to the ConfigureServices Method
services.AddSingleton(sp =>
sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);
appSettings.json
Add these lines of code to this file
"DatabaseSettings": {
"ConnectionString": "your connection string to MongoDb"
}
Database Settings.cs
public class DatabaseSettings
{
public string ConnectionString { get; set; }
}
MealPlanModel.cs:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
public class MealPlanModel
{
#region MealPlanModel fields
[BsonElement("userid")]
public int? UserId { get; set; }
[BsonElement("mealid")]
public int? MealId { get; set; }
[BsonElement("mealname")]
public string MealName { get; set; }
[BsonElement("mealimage")]
public string MealImage { get; set; }
[BsonElement("foods")]
public FoodModel[] Foods { get; set; }
[BsonElement("totalcalories")]
public double TotalCalories { get; set; }
[BsonElement("totalprotein")]
public double TotalProtein { get; set; }
[BsonElement("totalcarbs")]
public double TotalCarbs { get; set; }
[BsonElement("totalfat")]
public double TotalFat { get; set; }
[BsonElement("totalsugar")]
public double TotalSugar { get; set; }
[BsonElement("totalfiber")]
public double TotalFiber { get; set; }
#endregion
#region MealPlanModel ctor
public MealPlanModel()
{
}
public MealPlanModel(int userid, int mealid)
{
UserId = userid;
MealId = mealid;
}
}
MealPlanDto.cs
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Collections.Generic;
public class MealPlanDto
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("meals")]
public List<MealPlanModel> MealPlans { get; set; }
}
**MealsController.cs:**
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
[Route("api/[controller]")]
[ApiController]
public class MealsController : ControllerBase
{
private readonly IMealsRepository _repo;
private readonly IMapper _mapper;
public MealsController(IMealsRepository repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
[HttpGet, Route("CheckConnection")]
public async Task<IActionResult> CheckConnection()
{
var result = await _repo.CheckConnection();
if (result == null || result.Count <= 0)
return BadRequest("Failed to connect to database.");
return Ok("Database connection was successful");
}
[HttpPost("deletecustommeal")]
public async Task<IActionResult> DeleteCustomMealPlan(int id)
{
var requestBody = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
var mealPlanToDelete = JsonConvert.DeserializeObject<MealPlanModel>(requestBody);
MealPlanDto deleteMealPlan = new MealPlanDto();
deleteMealPlan.MealPlans = new List<MealPlanModel>();
deleteMealPlan.MealPlans.Add(mealPlanToDelete);
var result = await _repo.DeleteCustomMealPlanById(deleteMealPlan);
if (!result)
return BadRequest("Failed to delete meal");
return Ok("Successfully deleted meal plan");
}
}
MealsRepository.cs
using Microsoft.Extensions.Configuration;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class MealsRepository : IMealsRepository
{
private readonly MongoClient _client;
private readonly IMongoDatabase _database;
private readonly IMongoCollection<MealPlanDto> _userMealsCollection;
public MealsRepository(IConfiguration configuration)
{
_client = new
MongoClient(configuration.GetSection("DatabaseSettings").GetSection("ConnectionString").Value);
_database = _client.GetDatabase("MealsDb");
_userMealsCollection = _database.GetCollection<MealPlanDto>("meal");
}
public async Task<List<BsonDocument>> CheckConnection()
{
List<BsonDocument> list = await _database.ListCollections().ToListAsync();
var populatedList = (list != null && list.Count > 0) ? list : null;
return populatedList;
}
public async Task<bool> DeleteCustomMealPlanById(MealPlanDto mealPlanToDelete)
{
var builder = Builders<MealPlanDto>.Filter;
var filter = builder.Eq(x => x.MealPlans[0].UserId, mealPlanToDelete.MealPlans[0].UserId);
var update = Builders<MealPlanDto>.Update.PullFilter(
p => (IEnumerable<MealPlanModel>)p.MealPlans[0],
f => f.MealId.Value == mealPlanToDelete.MealPlans[0].MealId);
try
{
await _userMealsCollection.UpdateOneAsync(filter, update);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to delete meal plan. {ex} occured.");
return false;
}
}
}
Thanks for all that attempted to find an answer to my question above, but I actually discovered a simple solution
I simply replaced the above method in the Repository.cs file with the following and it works like a charm
public bool DeleteCustomMealPlanForUserById(MealPlanModel mealPlanToDelete)
{
var result = _customUserMealsCollection.DeleteOne(p => p.MealPlans[0].UserId == mealPlanToDelete.UserId
&& p.MealPlans[0].MealId == mealPlanToDelete.MealId);
return result.DeletedCount != 0;
}
I'm rewriting a current working POS system that was created in C# using the .NET Framework. When trying to build the project the compiler returns error CS0311 saying that the type of the parameter given can't be used.
I've researched the used classes and everything it needs according to the .NET documentation is defined in the class so I have no clue why it doesn't work.
The code used in EfDbContext.cs:
using NLog;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity.Validation;
using System.Linq.Expressions;
using Dal.Migrations;
using Dal.Model;
namespace Dal
{
public class EfDbContext : DbContext
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public EfDbContext()
: base(Settings.ConnectionString ?? "")
{
Database.SetInitializer<EfDbContext>((IDatabaseInitializer<EfDbContext>) new MigrateDatabaseToLatestVersion<EfDbContext, Configuration>());
EfDbContext.logger.Debug("Database entity context initialized");
}
public DbSet<Member> Members { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<TaxCategory> TaxCategories { get; set; }
public DbSet<Ticket> Tickets { get; set; }
public DbSet<TicketLine> TicketLines { get; set; }
public DbSet<MemberCard> MemberCards { get; set; }
public DbSet<CheckoutSheet> CheckoutSheets { get; set; }
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Ticket>().HasMany<TicketLine>((Expression<Func<Ticket, ICollection<TicketLine>>>)(p => p.TicketLines)).WithRequired().WillCascadeOnDelete(true);
modelBuilder.Entity<Ticket>().HasMany<Transaction>((Expression<Func<Ticket, ICollection<Transaction>>>)(p => p.Transactions)).WithOptional((Expression<Func<Transaction, Ticket>>)(m => m.Ticket)).WillCascadeOnDelete(true);
modelBuilder.Entity<Member>().HasMany<MemberCard>((Expression<Func<Member, ICollection<MemberCard>>>)(p => p.MemberCards)).WithRequired((Expression<Func<MemberCard, Member>>)(m => m.Member));
modelBuilder.Entity<CheckoutSheet>().HasMany<Ticket>((Expression<Func<CheckoutSheet, ICollection<Ticket>>>)(p => p.Tickets)).WithOptional((Expression<Func<Ticket, CheckoutSheet>>)(m => m.CheckoutSheet)).WillCascadeOnDelete(true);
}
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
throw new FormattedDbEntityValidationException(ex);
}
}
~EfDbContext()
{
EfDbContext.logger.Debug("Database entity context destroyed");
}
}
}
Code used in Configuration.cs:
using NLog;
using System.Data.Entity;
using System.Data.Entity.Migrations;
namespace Dal.Migrations
{
internal sealed class Configuration : DbMigrationsConfiguration<DbContext>
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public Configuration()
{
this.AutomaticMigrationsEnabled = true;
this.AutomaticMigrationDataLossAllowed = true;
this.ContextKey = "POS.Dal.EfDbContext";
}
protected override void Seed(DbContext context)
{
Configuration.logger.Info("Database migration completed");
}
}
}
[CS0311 error returned by compiler][1]
Error CS0311
The type 'Dal.Migrations.Configuration' cannot be used as type parameter 'TMigrationsConfiguration' in the generic type or method 'MigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>'. There is no implicit reference conversion from 'Dal.Migrations.Configuration' to 'System.Data.Entity.Migrations.DbMigrationsConfiguration<Dal.EfDbContext>'.
Dal 2
D:\lagae\Documents\Point Of Sale\POS\Dal
D:\lagae\Documents\Point Of Sale\POS\Dal\EfDbContext.cs
Line 20
Column 134
Does somebody see what I did wrong?
Kind Regards,
Jerlag_01
ASP.NET Core (Version: 2.2.102)
I am building an API to return Portos and Especies, but anytime that I access /api/portos (as defined in the controller), I get this error:
InvalidOperationException: Unable to resolve service for type
'AutoMapper.IMapper' while attempting to activate
'fish.Controllers.PortosController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider
sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
I am not sure what am I doing wrong, so any help is appreciated.
Models
Especie.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace fish.Models
{
[Table("Especies")]
public class Especie
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Nome { get; set; }
public Porto Porto { get; set; }
public int PortoId { get; set; }
}
}
Porto.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
namespace fish.Models
{
public class Porto
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Nome { get; set; }
public ICollection<Especie> Models { get; set; }
public Porto()
{
Models = new Collection<Especie>();
}
}
}
Controller
PortoController.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using fish.Controllers.Resources;
using fish.Models;
using fish.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace fish.Controllers
{
public class PortosController : Controller
{
private readonly FishDbContext context;
private readonly IMapper mapper;
public PortosController(FishDbContext context, IMapper mapper)
{
this.mapper = mapper;
this.context = context;
}
[HttpGet("/api/portos")]
public async Task<IEnumerable<PortoResource>> GetPortos()
{
var portos = await context.Portos.Include(m => m.Models).ToListAsync();
return mapper.Map<List<Porto>, List<PortoResource>>(portos);
}
}
}
Controller>Resources
PortoResources.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace fish.Controllers.Resources
{
public class PortoResource
{
public int Id { get; set; }
public string Nome { get; set; }
public ICollection<EspecieResource> Models { get; set; }
public PortoResource()
{
Models = new Collection<EspecieResource>();
}
}
}
EspecieResource.cs
namespace fish.Controllers.Resources
{
public class EspecieResource
{
public int Id { get; set; }
public string Nome { get; set; }
}
}
More Relevant Code
Stratup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
services.AddDbContext<FishDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
MappingProfile.cs
using AutoMapper;
using fish.Controllers.Resources;
using fish.Models;
namespace fish.Mapping
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Porto, PortoResource>();
CreateMap<Especie, EspecieResource>();
}
}
}
FishDbContext.cs
using fish.Models;
using Microsoft.EntityFrameworkCore;
namespace fish.Persistence
{
public class FishDbContext : DbContext
{
public FishDbContext(DbContextOptions<FishDbContext> options) : base(options)
{
}
public DbSet<Porto> Portos { get; set; }
}
}
You will have to use automapper package as shown below:
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
This will also in turn install the Automapper nuget package if you don’t have it already.
Then, inside your ConfigureServices method of your startup.cs, you will have to add a call to it as shown below.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
}
Refer this blog for more details.
EDIT:
There is very nice description from this thread.
You need to add code like below in startup.cs.
You have missed to add IMapper in DI. Please refer add Singleton call from below code.
public void ConfigureServices(IServiceCollection services) {
// .... Ignore code before this
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}
EDIT: 06-07-21:
Refer this blogpost which explains how to use AutoMapper with latest .NET
In case you are using .NET CORE 5. First go to nuget packages, find and install AutoMapper.Extensions.Microsoft.DependencyInjection
Then you should extend Profile from Automapper and write mappings for your classes. Something like this below
Then you should add Automapper in ConfigureServices method in Startup class.This
typeof(CommonMappingProfile) can be omitted if your nuget package for AutoMapper is in the same project as your mapping profile class.
services.AddAutoMapper(typeof(CommonMappingProfile));
Scenario: Intranet app. Windows authentication. EF 6.1.3. Databases: SQL Server Compact Edition and MS Access. VS Studio 2013.
The solution has 3 projects:
EnqueteWeb.UI - ASP.NET web application;
EnqueteWeb.Dominio - class library for the application domain;
ControleDeAcessoGeral - class library to get data of the user logged from Active Directory, and include/update/delete/list some users that perform some special actions on the app.
As the access control to the app is based on a SQL Server Compact Edition database, I have EntityFramework installed in ControleDeAcessoGeral. I want to have all the methods regarding to users in a class in this project. And so I did it.
This ControleDeAcessoGeral project is defined like this:
Aplicacao
- Corp.cs (methods to deal with Active Directory stuff)
- UsuariosApp.cs (methods to deal with the SQL Server CE database)
Contexto
- DBControleDeAcesso.cs (defines the context)
- InicializaControleDeAcesso.cs (fill in initial data to the
DBControleDeAcesso database)
Entidades
- Perfil.cs (profiles that a user can have on the app)
- Usuarios.cs (users that may perform some actions on the app)
- UsuarioAD.cs (Active Directory user and its data)
The DBControleDeAcesso.cs class has the following code:
using ControleDeAcessoGeral.Models.Entidades;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ControleDeAcessoGeral.Models.Contexto
{
public class DBControleDeAcesso : DbContext
{
public DBControleDeAcesso() : base("ControleDeAcessoContext") { }
public DbSet<Perfil> Perfis { get; set; }
public DbSet<Usuario> Usuarios { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
The entities classes are the following:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ControleDeAcessoGeral.Models.Entidades
{
public class Usuario
{
[Key]
public string Logon { get; set; }
public string Nome { get; set; }
[Display(Name="Órgão")]
public string Orgao { get; set; }
public string Email { get; set; }
[StringLength(maximumLength: 4)]
public string Depto { get; set; }
[Display(Name = "Perfis")]
public virtual List<Perfil> Perfis { get; set; }
public Usuario()
{
this.Perfis = new List<Perfil>();
}
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ControleDeAcessoGeral.Models.Entidades
{
public class Perfil
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "Por favor, informe o NOME DO perfil.")]
[StringLength(maximumLength: 25)]
public string Nome { get; set; }
[StringLength(maximumLength: 255)]
[Display(Name = "Descrição")]
public string Descricao { get; set; }
public virtual List<Usuario> Usuarios { get; set; }
public Perfil()
{
this.Usuarios = new List<Usuario>();
}
}
}
And the UsuariosApp.cs class is as bellow (for the sake of brevity, I'll show only the methods that concerns to the issue):
using ControleDeAcessoGeral.Models.Contexto;
using ControleDeAcessoGeral.Models.Entidades;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace ControleDeAcessoGeral.Models.Aplicacao
{
public class UsuariosApp
{
private DBControleDeAcesso db { get; set; }
public UsuariosApp()
{
db = new DBControleDeAcesso();
}
public void SalvarUsuario(Usuario usuario)
{
db.Usuarios.Add(usuario);
db.SaveChanges();
}
public Perfil LocalizarPerfil(int id)
{
return db.Perfis.Find(id);
}
}
}
The action that tries to save a user (Usuarios.cs) in the SQL Server CE database is in AdministracaoController and has the following code:
using ControleDeAcessoGeral.Models.Aplicacao;
using ControleDeAcessoGeral.Models.Entidades;
using EnqueteWeb.UI.Models;
using EnqueteWeb.UI.ViewModels;
using System.Linq;
using System.Web.Mvc;
namespace EnqueteWeb.UI.Controllers
{
public class AdministracaoController : Controller
{
[HttpPost]
public ActionResult CriarUsuarioNaApp(UsuarioViewModel model)
{
foreach (var item in model.PerfisSelecionados)
{
Perfil perfil = new UsuariosApp().LocalizarPerfil(item);
model.Usuario.Perfis.Add(perfil);
}
if (ModelState.IsValid)
{
new UsuariosApp().SalvarUsuario(model.Usuario);
return RedirectToAction("Usuarios");
}
return View(model);
}
}
}
So, when this action CriarUsuarioNaApp is invoked and the method SalvarUsuario(model.Usuario) runs, the following error occurs:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
I've read a few about this on web but, unfortunately, I still couldn't make it works.
Hope a wise and good soul will show me the way.
Thanks for your attention.
Paulo Ricardo Ferreira
The problem arises from the fact that you do not dispose of the first DbContext instance (from which you load the profile entities) prior to attaching said entities to the second DbContext instance.
To fix (and some additional suggestions):
have UsuariosApp implement IDisposable and dispose your instance of the DbContext db when disposing UsuariosApp
wrap your newly-disposable UsuariosApp instance in a using statement and use this single instance for both your Perfil loading and Usuario saving logic
optimize Perfil loading by loading all values with single call
validate ModelState.IsValid immediately
Something like this:
public class UsuariosApp : IDisposable
{
private DBControleDeAcesso db { get; set; }
public UsuariosApp()
{
db = new DBControleDeAcesso();
}
public void SalvarUsuario(Usuario usuario)
{
db.Usuarios.Add(usuario);
db.SaveChanges();
}
public Perfil LocalizarPerfil(int id)
{
return db.Perfis.Find(id);
}
public IEnumerable<Perfil> LocalizarPerfiles( IEnumerable<int> ids )
{
return db.Perfils.Where( p => ids.Contains( p.Id ) )
.ToArray();
}
private bool _disposed = false;
protected virtual void Dispose( bool disposing )
{
if( _disposed )
{
return;
}
if( disposing )
{
db.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
}
public ActionResult CriarUsuarioNaApp( UsuarioViewModel model )
{
// validate model state first
if( ModelState.IsValid )
{
// use single, disposable repo/uow instance
using( var uapp = new UsuariosApp() )
{
// get all profiles in a single call, no loop required
var perfils = uapp.LocalizarPerfiles( model.PerfisSelecionados );
model.Usuario.Perfis.AddRange( perfils );
uapp.SalvarUsuario( model.Usuario );
}
return RedirectToAction( "Usuarios" );
}
return View( model );
}
Let me know if that doesn't solve your problem.
Hey Guys I hava a question. I know questions like this are asked often, but I worked on a solution for several hours and read many answers but I couldnt find the right one. I am doing an application using ASP.NET MVC 4 Razor. I´m rather new to this system. I created a .edmx Data Model using Entity Framework 5 (Database-First Approach). This is how my auto-generated Context class looks like:
namespace KSM3.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
public partial class kontrollsystemEntities : DbContext
{
public kontrollsystemEntities()
: base("name=kontrollsystemEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
[EdmFunction("kontrollsystemEntities", "udf_GetReportsByController")]
public virtual IQueryable<udf_GetReportsByController_Result> udf_GetReportsByController(string controller_account)
{
var controller_accountParameter = controller_account != null ?
new ObjectParameter("controller_account", controller_account) :
new ObjectParameter("controller_account", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<udf_GetReportsByController_Result>("[kontrollsystemEntities].[udf_GetReportsByController](#controller_account)", controller_accountParameter);
}
}
}
and my Model class looks like this:
namespace KSM3.Models
{
using System;
public partial class udf_GetReportsByController_Result
{
public int ID { get; set; }
public string ProviderID { get; set; }
public int VertragID { get; set; }
public System.DateTime Leistungszeitraum_von { get; set; }
public System.DateTime Leistungszeitraum_bis { get; set; }
public string ReportklasseID { get; set; }
public int Version { get; set; }
public string Status { get; set; }
}
}
When I now click on "Add Controller" and select my classes, I get the error message:
"Unable to retrieve Metadata for KSM3.Models.udf_GetReportsByController_Result.cs"
Note: I am using Entity Framework to retrieve information from a user-defined function, not from a table! If I try the same procedure with a table, it works!
What do I have to prepare or change in order to make this work?
Thank you for all answers!
I have solved my problem, thanks!
I had to call the udf_GetReportsByController(string controller_account) method in the controller and hand the IQueryable-Result to my view.
My Controller looks like these (Note: Beginner´s mistake)
public class ReportController : Controller
{
private kontrollsystemEntities db = new kontrollsystemEntities();
//
// GET: /Report/
public ActionResult Index()
{
IQueryable<udf_GetReportsByController_Result> result = db.udf_GetReportsByController(User.Identity.Name);
return View(result.ToList());
}
}
}