IdentityRole Navigational Property Exception Using Entity Framework - asp.net-mvc

I am having an issue mapping the "IdentityRole" property to my ApplicationUsers class. I am receiving an error to the effect of:
++++
The declared type of navigation property WebApp.Core.DAL.ApplicationUsers.IdentityRoles is not compatible with the result of the specified navigation.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.Entity.Core.MetadataException: The declared type of navigation property WebApp.Core.DAL.ApplicationUsers.IdentityRoles is not compatible with the result of the specified navigation.
Source Error:
Line 28: using (CMSContext cntx = new CMSContext())
Line 29: {
Line 30: var users = cntx.Users
Line 31: .Include(m => m.IdentityRoles)
Line 32: .Include(s => s.AspNetUsersSites)
Source File: c:\LocalSites\WebApp\1.0.0.0\DAL\UserManagement\User.cs Line: 30
Stack Trace:
[MetadataException: The declared type of navigation property WebApp.Core.DAL.ApplicationUsers.IdentityRoles is not compatible with the result of the specified navigation. ]
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.ValidateNavPropertyOp(PropertyOp op) +401
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.Visit(PropertyOp op, Node n) +80
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitChildren(Node n) +163
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.VisitScalarOpDefault(ScalarOp op, Node n) +33
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitChildren(Node n) +163
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitDefault(Node n) +22
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitAncillaryOpDefault(AncillaryOp op, Node n) +21
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitChildren(Node n) +163
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitDefault(Node n) +22
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitAncillaryOpDefault(AncillaryOp op, Node n) +21
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitChildren(Node n) +163
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitRelOpDefault(RelOp op, Node n) +38
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.Visit(ProjectOp op, Node n) +599
System.Data.Entity.Core.Query.PlanCompiler.SubqueryTrackingVisitor.VisitChildren(Node n) +163
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitDefault(Node n) +22
System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfNode.VisitPhysicalOpDefault(PhysicalOp op, Node n) +21
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.Process(Dictionary`2& tvfResultKeys) +106
System.Data.Entity.Core.Query.PlanCompiler.PreProcessor.Process(PlanCompiler planCompilerState, StructuredTypeInfo& typeInfo, Dictionary`2& tvfResultKeys) +54
System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Compile(List`1& providerCommands, ColumnMap& resultColumnMap, Int32& columnCount, Set`1& entitySets) +236
System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition..ctor(DbProviderFactory storeProviderFactory, DbCommandTree commandTree, DbInterceptionContext interceptionContext, IDbDependencyResolver resolver, BridgeDataReaderFactory bridgeDataReaderFactory, ColumnMapFactory columnMapFactory) +441
++++
I have lazy loading off and my ApplicationUsers class looks like this
namespace WebApp.Core.Contracts
{
public class ApplicationUsers : IdentityUser
{
public bool IsActive { get; set; }
public string LockoutReason { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Photo { get; set; }
public virtual List<AspNetUsersSites> AspNetUsersSites { get; set; }
// a collection of roles that can be written to, since it needs to be evaluated earlier before it's disposed of
[ForeignKey("Id")]
public virtual List<IdentityRole> IdentityRoles { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUsers> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public async Task<IList<string>> GenerateUserRolesAsync(UserManager<ApplicationUsers> manager, string userId)
{
var userRoles = await manager.GetRolesAsync(this.Id);
return userRoles;
}
public async Task DeleteUser(UserManager<ApplicationUsers> manager, string username)
{
var user = manager.FindByName(username);
await manager.DeleteAsync(user);
}
}
My Linq query looks like this
public static List<Contracts.ApplicationUsers> GetUsersForSiteWithRoles(int SiteID){
using (CMSContext cntx = new CMSContext())
{
var users = cntx.Users
.Include(m => m.IdentityRoles)
.Include(s => s.AspNetUsersSites)
.Where(i => i.AspNetUsersSites.Where(s => s.SiteID == SiteID).Count() > 0).ToList();
return users;
}
}
What is wrong with the navigational property I added and/or the query. All I am really trying to do is return users with their roles that are stored in my database.
Thank you!

First, it should be:
public virtual ICollection<IdentityRole> IdentityRoles { get; set; }
Not List<IdentityRole>.
Second, this relationship already exists on ApplicationUser through inheritance from IdentityUser in the Roles property. You're creating a secondary relationship here that will never be used by the Identity framework.
Also, for what it's worth, if you want your AspNetUserSites to follow the table name scheme of the Identity tables, you need only specify the Table attribute on the class. Naming your class in this way is obtuse. For example:
[Table("AspNetUserSites")]
public class Site
{
...
}
Then you just have a nice class like Site instead of AspNetUserSite
UPDATE
IdentityUserRole is just the join table between IdentityUser and IdentityRole. In the default implementation there's not a whole lot of point in its existence, but you could potentially extend the Identity classes to attach additional information on the relationship that way.
Anyways, if you just want to get at the actual IdentityRoles, just do something like:
var roleIds = user.Roles.Select(r => r.RoleId).ToList();
var roleNames = db.Roles.Where(r => roleIds.Contains(r.Id)).Select(r => r.Name);

Related

How do I include a [NotMapped] property in an EF business object without getting FirstChance IndexOutofRangeException?

I was wondering why my XAF WinForms EF application was slow loading a detail view.
Then I learned how to capture FirstChance Exceptions and discovered I was experiencing an IndexOutOfRange exception as described here
Sometimes I want to include a non mapped property in my business object such as Job in example.
public class OrderLineResult
{
public int LineId { get; set; }
public int Quantity { get; set; }
public string Description { get; set; }
[Browsable(false)] public int JobId { get; set; }
[NotMapped] [Browsable(false)] public virtual Job Job { get; set; }
}
And I have a method to get the data inside the OrderLineResult class
public static OrderLineResult[] GetData(int headId)
{
using var connect = new MyDbContext()
const string sql =
#"SET NOCOUNT ON;
create table #temp( JobId int, Quantity int, LineId int, Description )
/* code to populate the table */
select JobId,LineId,Quantity, Description from #temp"
var results = connect.Database.SqlQuery<OrderLineResult>(sql,headId).ToArray();
return results.ToArray();
}
}
Yet the IndexOutOfRange exception occurs for the Job property.
The call stack is
System.IndexOutOfRangeException
Job
at MyApp.Module.Win.Controllers.ToDoList.TaskActionController.<>c.<actExceptions_Execute>b__34_0(Object sender, FirstChanceExceptionEventArgs e)
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at System.Data.Entity.Core.Query.InternalTrees.ColumnMapFactory.TryGetColumnOrdinalFromReader(DbDataReader storeDataReader, String columnName, Int32& ordinal)
at System.Data.Entity.Core.Query.InternalTrees.ColumnMapFactory.CreateColumnMapFromReaderAndClrType(DbDataReader reader, Type type, MetadataWorkspace workspace)
at System.Data.Entity.Core.Objects.ObjectContext.InternalTranslate[TElement](DbDataReader reader, String entitySetName, MergeOption mergeOption, Boolean streaming, EntitySet& entitySet, TypeUsage& edmType)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryInternal[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass186_0`1.<ExecuteStoreQueryReliably>b__1()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass186_0`1.<ExecuteStoreQueryReliably>b__0()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryReliably[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQuery[TElement](String commandText, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at MyApp.Module.BusinessObjects.NonPersistedBusinessObjects.OrderLineResult.GetData(Int32 headId)
I am using EntityFramework 6.4.4 and .Net Framework 4.7.2
This feels like a cludge,
I added
,null as job
to the last select statement

Service provider exception on registering a user manager for a template user

I am learning ASP .NET Core and today I have stumbled across something. I have User class which inherits IdentityUser and adds some custom fields, such as first name, last name, etc...
The User class is further extended by other types of users, which only add one or two extra fields, at most. I have decided to take this route as it was unnecessary to repeat the same code in 4 places.
I have defined a UserRepository which implements an interface IUserRepository<TEntity> where TEntity : User. Whenever I try to access the Index, I get an exception like:
InvalidOperationException: No service for type 'Microsoft.AspNetCore.Identity.UserManager`1[HM.models.users.Medic]' has been registered.
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<T>(IServiceProvider provider)
HM.repositories.UserRepository<TEntity>..ctor(ApplicationDbContext context, IServiceProvider serviceProvider, string role) in UserRepository.cs
+
_userManager = serviceProvider.GetRequiredService<UserManager<TEntity>>();
HM.repositories.MedicRepository..ctor(ApplicationDbContext context, IServiceProvider serviceProvider) in MedicRepository.cs
+
public MedicRepository(ApplicationDbContext context, IServiceProvider serviceProvider) : base(context, serviceProvider, _role) { }
HM.persistence.UnitOfWork..ctor(ApplicationDbContext context, IServiceProvider _serviceProvider) in UnitOfWork.cs
+
Medics = new MedicRepository(_context, _serviceProvider);
app.Controllers.MedicController.Index() in MedicController.cs
+
using (var unitOfWork = new UnitOfWork(_context, _serviceProvider))
Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Those are some code snippets:
MedicController:
// GET: Medic
public async Task<IActionResult> Index()
{
using (var unitOfWork = new UnitOfWork(_context,_serviceProvider))
{
return View(await unitOfWork.Medics.GetAll());
}
}
UserRepository and IUserRepository:
public interface IUserRepository<TEntity> where TEntity : User
{
Task Add(TEntity user, string password);
Task Remove(TEntity user);
Task Update(TEntity user);
Task<IEnumerable<TEntity>> GetAll();
Task<TEntity> GetById(string id);
Task<bool> Any(Func<TEntity, bool> predicate);
}
public class UserRepository<TEntity> : IUserRepository<TEntity> where TEntity : User
{
private readonly string _role;
private readonly UserManager<TEntity> _userManager;
public UserRepository(ApplicationDbContext context, IServiceProvider serviceProvider, string role)
{
_role = role;
_userManager = serviceProvider.GetRequiredService<UserManager<TEntity>>();
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await _userManager.GetUsersInRoleAsync(_role);
}
}
Finally, the medic:
[Table("Medic")]
public class Medic : User
{
[DisplayName("Departments")]
public ICollection<MedicDepartment> departments { get; set; }
[DisplayName("Diagnostics")]
public ICollection<MedicDiagnostic> diagnostics { get; set; }
[PersonalData]
[DisplayName("Rank")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The rank name must be between 3 and 30 characters long!")]
public string rank { get; set; }
}
I have debugged the application: it will throw this exception at _userManager = serviceProvider.GetRequiredService<UserManager<TEntity>>(); inside UserRepository. I don't understand why this hapens since
I have clearly stated where TEntity : User.
Thanks!
P.S. I have removed some code and some emthods to make this post more redable.
P.S.S: The MedicRepository class extends UserRepository and calls base and contains, for now, nothing else. The UnitOfWork contains all the application repositories and calls new on each one of them inside its contructor.
P.S.S.S. I wanted to use templates for this repository in order to avoid casting inside controllers. It used to return 'User' related data.
I figured it out. It was not enough to register a service for UserManager<User>, but I also had to register a UserManager for each type of user which inherited User.
First, I added those lines to Startup.cs inside ConfigureServices. This used IdentityCore instead Identity.
services.AddIdentityCore<Medic>() //add the derived user type from custom user
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<Medic, IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
Secondly, don't forget to add the managers. Same as before, in the same file and method, add:
services.AddScoped<UserManager<User>, UserManager<User>>(); //the user manager for the base type of User
services.AddScoped<UserManager<Medic>, UserManager<Medic>>(); //the user manager for Medics
Hope it helps!

Problems implementing a simple ASP.NET Core Web API Service (PersonExample)

I am new to ASP.NET, ASP.NET MVC and Entity Framework (generally) and with the .NET Core variants (specifically). Currently I am trying to get my first example/test project running (in Visual Studio 2015) but having a couple of problems I couldn't find solutions for on Google.
Part of the tutorials & instructions I followed so far:
https://dzone.com/articles/how-to-create-rest-apiweb-api-with-aspnet-core-10 (for the first introduction)
http://www.restapitutorial.com/lessons/httpmethods.html (for what the web api should return)
https://docs.efproject.net/en/latest/platforms/aspnetcore/existing-db.html (create the DB and scaffold-dbcontext)
https://docs.asp.net/en/latest/fundamentals/logging.html (for the general use of loggers)
https://github.com/NLog/NLog.Extensions.Logging (for configuring Logging with NLog)
https://docs.asp.net/en/latest/tutorials/web-api-help-pages-using-swagger.html (for setting up and using swagger)
Those tutorials & instructions only describe a snippet of the solution each but those snippets do not fit together and cause problems. So I am trying to get the missing pieces together.
What I want to achieve is a (as simple as possible) example project:
ASP.NET Core Web API demo/example project (in Visual Studio 2015)
which stores data in a (SQL) database (not some handwritten repository) using Entity Framework Core (just 1 table Person holding 3 columns: id as primary key identity, 2 columns firstname and lastname defined as nvarchar(30))
where one can
request (GET) all persons (WORKS in the code below)
(GET) a specific person by id or by lastname (works in the code below)
create (POST) a new person (works in the code below)
(DELETE) a person by id (works in the code below)
full replace (PUT) by id (HOW TO DO?)
modify (PATCH) the last name (people still marry) only sending id and new last name (HOW TO DO?)
using a repository between the controller and the dbContext (for reusability of the repository functions)
have the controller to be standard conform (return correct error code/error results)
have working exception handling
My implementation in question:
IPersonRepository interface:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PersonExample.Models;
namespace PersonExample.Repository
{
public interface IPersonRepositoy
{
IEnumerable<Person> GetAll();
Person GetById(int id);
IEnumerable<Person> GetByLastname(string lastname);
IEnumerable<Person> SearchByLastname(string namePart);
int Create(Person item);
int Delete(int id);
int Replace(int id, Person item);
int Modify(int id, string newLastname);
}
}
PersonRepository implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PersonExample.Models;
namespace PersonExample.Repository
{
public class PersonRepository : IPersonRepositoy
{
private readonly PersonDbContext _dbContext;
private readonly ILogger<PersonRepository> _logger;
public PersonRepository(PersonDbContext dbContext, ILogger<PersonRepository> logger)
{
_dbContext = dbContext;
_logger = logger;
}
public IEnumerable<Person> GetAll()
{
//always returns an IEnumerable (even if it is empty)
_logger.LogDebug(string.Format("{0}.GetAll()", GetType().Name));
return _dbContext.Person;
}
public Person GetById(int id)
{
//SingleOrDefault() returns an instance of Person or null
_logger.LogDebug(string.Format("{0}.GetById({1})", GetType().Name, id));
return _dbContext.Person.Where(i => i.Id == id).SingleOrDefault();
}
public IEnumerable<Person> GetByLastname(string lastname)
{
//always returns an IEnumerable (even if it is empty)
_logger.LogDebug(string.Format("{0}.GetByLastname({1})", GetType().Name, lastname));
return _dbContext.Person.Where(i => i.Lastname == lastname);
}
public IEnumerable<Person> SearchByLastname(string namePart)
{
//always returns an IEnumerable (even if it is empty)
_logger.LogDebug(string.Format("{0}.SearchByLastname({1})", GetType().Name, namePart));
return _dbContext.Person.Where(i => i.Lastname.Contains(namePart));
}
public int Create(Person item)
{
_logger.LogDebug(string.Format("{0}.Create({1}) (id: {2}, firstname: {3}, lastname: {4})",
GetType().Name, item, item.Id, item.Firstname, item.Lastname));
//Add seems to be atomic > Attach would save linked objects too but seems to fail on simple objects
//what exceptions could occur to catch somewhere else (e.g. if lastname would have a unique constraint)?
_dbContext.Person.Add(item);
int res;
try
{
res = _dbContext.SaveChanges();
}
catch (Microsoft.EntityFrameworkCore.DbUpdateException e)
{
_logger.LogError(string.Format("", GetType().Name));
res = -1;
}
if (res == 0)
{
_logger.LogError(string.Format("{0}.Create({1}) -> no items were created/changed", GetType().Name, item));
}
else
{
_logger.LogDebug(string.Format("{0}.Create({1}) -> {2} item(s) were created/changed", GetType().Name, item, res));
}
return res;
}
public int Delete(int id)
{
_logger.LogDebug(string.Format("{0}.Delete({1}", GetType().Name, id));
Person item = _dbContext.Person.Where(i => i.Id == id).SingleOrDefault();
if (item != null)
{
_dbContext.Person.Remove(item);
int res = _dbContext.SaveChanges();
if (res == 0)
{
_logger.LogError(string.Format("{0}.Delete({1} -> no items deleted", GetType().Name, id));
}
else
{
_logger.LogDebug(string.Format("{0}.Delete({1} -> {2} item(s) deleted", GetType().Name, id, res));
}
return res;
}
else
{
_logger.LogError(string.Format("{0}.Delete({1} -> not item found by id", GetType().Name, id));
return -1; // better way to indicate not found?
}
}
public int Replace(int id, Person item)
{
// how to implement replace
throw new NotImplementedException();
}
public int Modify(int id, string newLastname)
{
// how to implement modify
throw new NotImplementedException();
}
}
}
PersonController:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using PersonExample.Repository;
using PersonExample.Models;
namespace PersonExample.Controllers
{
[Route("api/[controller]")]
public class PersonController : Controller
{
private readonly IPersonRepositoy _repo;
private readonly ILogger<PersonRepository> _logger;
public PersonController(IPersonRepositoy repo, ILogger<PersonRepository> logger)
{
_repo = repo;
_logger = logger;
}
// GET: api/values
[HttpGet]
public IEnumerable<Person> Get()
{
_logger.LogDebug(string.Format("{0}.GetAll()", GetType().Name));
IEnumerable<Person> data = _repo.GetAll();
_logger.LogDebug(string.Format("{0}.GetAll() -> returned {1} result(s)", GetType().Name, "?"));
return data;
}
// GET api/values/5
[HttpGet("{id:int}", Name = "GetPerson")]
public IActionResult Get(int id)
{
_logger.LogDebug(string.Format("{0}.GetById({1})", GetType().Name, id));
Person item = _repo.GetById(id);
if (item == null)
{
_logger.LogError(string.Format("{0}.GetById({1}) -> no item found by id", GetType().Name, id));
return NotFound(id);
}
return new ObjectResult(item);
}
[HttpGet("{lastname}")]
public IEnumerable<Person> Get(string lastname)
{
// example to demonstrate overloading of types (int for id, string for lastname)
_logger.LogDebug(string.Format("{0}.GetByLastname()", GetType().Name));
IEnumerable<Person> data = _repo.GetByLastname(lastname);
_logger.LogDebug(string.Format("{0}.GetByLastname() -> returned {1} result(s)", GetType().Name, "?"));
return data;
}
[HttpGet("search/{namepart}")]
public IEnumerable<Person> Search(string namepart)
{
//example to demonstrate url modification (how would I do multiple name parts?)
_logger.LogDebug(string.Format("{0}.Search({1})", GetType().Name, namepart));
IEnumerable<Person> data = _repo.SearchByLastname(namepart);
_logger.LogDebug(string.Format("{0}.Search({1}) -> returned {2} result(s)", GetType().Name, namepart, "?"));
return data;
}
// POST api/values
[HttpPost]
public IActionResult Post([FromBody]Person value)
{
//how to validate data and what to return in error cases?
_logger.LogDebug(string.Format("{0}.Post({1})", GetType().Name, value));
if (value == null)
{
_logger.LogDebug(string.Format("{0}.Post({1}) -> bad request: item is null", GetType().Name, value));
return BadRequest();
}
//return 409 Conflict if resource exists -> where and how to check?
int res = _repo.Create(value);
if (res == 0) //no items changed
{
_logger.LogError(string.Format("{0}.Post({1}) -> zero items changed", GetType().Name, value));
return NotFound(); //what to return? not found isn't the problem
}
else if (res == -1) //DbUpdateException
{
_logger.LogError(string.Format("{0}.Post({1}) -> DbUpdateException", GetType().Name, value));
return NotFound(); //what to return? not found isn't the problem
}
_logger.LogDebug(string.Format("{0}.Post({1}) -> {2} items changed", GetType().Name, value, res));
return CreatedAtRoute("GetPerson", new { id = value.Id }, value);
}
// DELETE api/values/5
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_logger.LogDebug(string.Format("{0}.Delete(id: {1})", GetType().Name, id));
int res = _repo.Delete(id);
if (res == 0) // zero entries changed
{
_logger.LogError(string.Format("{0}.Delete({1}) -> zero items changed", GetType().Name, id));
//what to return in that case, its a different error than not found???
return NotFound();
}
else if (res == -1) // id not found
{
_logger.LogError(string.Format("{0}.Delete({1}) -> not found item by id", GetType().Name, id));
return NotFound(id);
}
return Ok();
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]Person value)
{
//example for full update / complete replace with logging and error handling
// how to implement, what to return?
// _repo.Replace(id, value);
}
// PATCH api/values/5
[HttpPatch("{id}")]
public void Patch(int id, [FromBody]Person value)
{
//example for partial update with logging and error handling
// how to implement, what to return?
//_repo.Modify(id, lastname);
}
}
}
My Questions
In general:
What are the correct (and REST standard conform) implementations of the controller and the repository including exception handling, data validation (necessary?) and logging of errors (when one occurs)

Edit entity in Ef - UnitOfWork

I am using this template for my project
public interface IUnitOfWork
{
IDbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges();
void RejectChanges();
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
}
Implementation:
public class BookStoreDbContext : DbContext, IUnitOfWork
{
public DbSet<Categori> Categoris { get; set; }
public new DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class
{
return base.Entry(entity);
}
public override int SaveChanges()
{
return base.SaveChanges();
}
Controler:
public class CategoriController : Controller
{
private IUnitOfWork _uw;
private ICategoriService _categoriService;
public CategoriController(IUnitOfWork uw,ICategoriService categoriservice )
{
_uw = uw;
_categoriService = categoriservice;
}
public ActionResult Edit(int id = 0)
{
var categori = _categoriService.Find(i => i.Id == id);
if (categori == null)
{
return HttpNotFound();
}
return View(categori);
}
[HttpPost]
public ActionResult Edit(Categori categori)
{
if (ModelState.IsValid)
{
_uw.Entry(categori).State = EntityState.Modified;
_uw.SaveChanges();
}
return View(categori);
}
}
Repository or Servis layer:
public interface IGenericService<T> : IDisposable where T : class
{
void Add(T entity);
void Delete(T entity);
T Find(Func<T, bool> predicate);
IList<T> GetAll();
IList<T> GetAll(Func<T, bool> predicate);
}
public interface ICategoriService : IGenericService<DomainClasses.Models.Categori>
{
}
impliment repository:
public class EfGenericService<TEntity> : IGenericService<TEntity> where TEntity : class
{
protected IUnitOfWork _uow;
protected IDbSet<TEntity> _tEntities;
public EfGenericService(IUnitOfWork uow)
{
_uow = uow;
_tEntities = _uow.Set<TEntity>();
}
public virtual void Add(TEntity entity)
{
_tEntities.Add(entity);
}
public void Delete(TEntity entity)
{
_tEntities.Remove(entity);
}
public TEntity Find(Func<TEntity, bool> predicate)
{
return _tEntities.Where(predicate).FirstOrDefault();
}
public IList<TEntity> GetAll()
{
return _tEntities.ToList();
}
public IList<TEntity> GetAll(Func<TEntity, bool> predicate)
{
return _tEntities.Where(predicate).ToList();
}
public class EfCategoriService : EfGenericService<Categori>,ICategoriService
{
public EfCategoriService(IUnitOfWork uow)
: base(uow)
{
}
}
Global.asax
private static void InitStructureMap()
{
ObjectFactory.Initialize(
x =>
{
x.For<IUnitOfWork>().HttpContextScoped().Use(() => new BookStoreDbContext());
x.ForRequestedType<ServiceLayer.Interfaces.ICategoriService>()
.TheDefaultIsConcreteType<EfCategoriService>();
}
But I get this error when update entity:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries
Please help me to resolve this error?
The only relevant lines in your snippets are:
_uw.Entry(categori).State = EntityState.Modified;
_uw.SaveChanges();
Now, look at the exception you get:
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded.
Does setting the entity state to Modified insert an entity? No.
Does it delete an entity? No.
Does it update an entity? Yes.
May the entity that EF tries to update have been deleted? Well, perhaps. How to check that? When an entity is deleted the database must know the key in order to know which row to delete. To confirm if the key is correct use a debugger in your controller post action, inspect the key value of categori that is passed into the method. Does it have the expected value? If not, you probably have a problem in your view or with binding the form and route values to the categori model. If yes, check in the database if the entity with that key is in the database table. If yes, next point.
May the entity have been modified? It could happen that EF "thinks" it has been modified in the database (even if it hasn't) if you have marked another property in your Categori model as a concurrency token. If that property has changed in the database or in the view between loading the entity in the GET request and reattaching (setting the state to Modified) and SaveChanges in the POST request you'll get a concurrency violation.
Priority has the test in bold above because it is the most likely cause of the problem in my opinion. If it turns out that the key doesn't have the expected value better ask a new question because it will be a pure ASP.NET MVC question that has nothing to do with EF and your UOW and service architecture.

What's wrong with my AutoMapper Customer ValueResolver Hook?

I have the following hook in my Global.aspx
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
AutoMapper.Mapper.CreateMap<FormCollection, Models.IAmACustomer>().ForAllMembers(form => form.ResolveUsing<Models.FormCollectionValueResolver<Models.IAmACustomer>>());
}
In My controller:
[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
var customer = AutoMapper.Mapper.Map<FormCollection,Models.IAmACustomer> (formCollection,null);
}
This line executes but my custom resolver is never called.
The resolver looks like this:
public class FormCollectionValueResolver<TDestination>:ValueResolver<FormCollection,TDestination>
{
//Code removed for brevity
}
The application compiles and runs, however without the custom resolver, nothing comes into the object, it just creates a mock object with exception throwing get accessors.
The reason the FormCollectionValueResolver<Customer> never gets called is that the ForAllMembers() method iterates over all your property mappings, as defined by the ForMember() method, applying the specified member options. However, in the code sample you supplied no property mappings have been defined, thus the resolver never gets called.
Here is an example of how the ForAllMembers() method could be used.
[Test]
public void AutoMapperForAllMembersTest()
{
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Sum,
opt => opt.ResolveUsing<AdditionResolver>())
.ForMember(dest => dest.Difference,
opt => opt.ResolveUsing<SubtractionResolver>())
.ForAllMembers(opt => opt.AddFormatter<CustomerFormatter>());
Source source = new Source();
source.Expression = new Expression
{
LeftHandSide = 2,
RightHandSide = 1
};
Destination destination = Mapper.Map<Source, Destination>(source);
Assert.That(destination.Sum, Is.EqualTo("*3*"));
Assert.That(destination.Difference, Is.EqualTo("*1*"));
}
public class Expression
{
public int LeftHandSide { get; set; }
public int RightHandSide { get; set; }
}
public class Source
{
public Expression Expression { get; set; }
}
public class Destination
{
public string Sum { get; set; }
public string Difference { get; set; }
}
public class AdditionResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
Expression expression = source.Expression;
return expression.LeftHandSide + expression.RightHandSide;
}
}
public class SubtractionResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
Expression expression = source.Expression;
return expression.LeftHandSide - expression.RightHandSide;
}
}
public class CustomerFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
return string.Format("*{0}*", context.SourceValue);
}
}
You should consider ditching FormCollection altogether:
Link
Basically, you'll lean on strongly-typed views + custom-created ViewModel types for forms. These forms have things like validation attributes on them so you can run them through validation frameworks. If it's valid, only then do you update your persistence model from the posted form. We stay away from creating domain objects directly from the posted form.

Resources