Problems implementing a simple ASP.NET Core Web API Service (PersonExample) - asp.net-mvc

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)

Related

Can't convert from int to "classname"

While building a ASP.NET-MVC website I was trying to implement a EDIT page on my website, but I'm having some difficulties, particulary in my Controller. The error is in this class, pointing to the ID:
public IActionResult Edit(int ID = 0)
{
GestaoAlertas _teste = addresses.Contains(ID);
return View(_teste);
}
And the error says:
It is not possible to convert from int to "hdsportal.GestaoAlertas"
Controller:
public class HomeController : Controller
{
SqlCommand com = new SqlCommand();
SqlDataReader dr;
SqlConnection con = new SqlConnection();
List<GestaoAlertas> addresses = new List<GestaoAlertas>();
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
con.ConnectionString = "secret";
}
public IActionResult Gestao_Alertas()
{
FetchData();
return View(addresses);
}
[Route("")]
public IActionResult Index()
{
FetchData();
return View(addresses);
}
public IActionResult Edit(int ID = 0)
{
GestaoAlertas _teste = addresses.Contains(ID);
return View(_teste);
}
public IActionResult Privacidade()
{
return View();
}
public IActionResult Gestao_Utilizadores()
{
return View();
}
public IActionResult Contatos()
{
return View();
}
public IActionResult QuemSomos()
{
return View();
}
private void FetchData()
{
if (addresses.Count > 0)
{
addresses.Clear();
}
try
{
con.Open();
com.Connection = con;
com.CommandText = "SELECT [ID], [SYSTEM_NAME], [SYSTEM_STATUS], [SYSTEM_SHORTMSG] FROM [CORE_SYS_STATUS]";
dr = com.ExecuteReader();
while (dr.Read())
{
addresses.Add(new GestaoAlertas()
{
ID = (int)dr["ID"]
,
SYSTEM_NAME = dr["SYSTEM_NAME"].ToString()
,
SYSTEM_STATUS = dr["SYSTEM_STATUS"].ToString()
,
SYSTEM_SHORTMSG = dr["SYSTEM_SHORTMSG"].ToString()
});
}
con.Close();
}
catch (Exception ex)
{
throw ex;
}
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Model:
public class GestaoAlertas
{
public int ID { get; set; }
public string SYSTEM_NAME { get; set; }
public string SYSTEM_STATUS { get; set; }
public string SYSTEM_SHORTMSG { get; set; }
}
Issue & Concern
According to List<T>.Contains(T), you need parse T object which is GestaoAlertas object to your addresses (List<GestaoAlertas> type).
Hence, you get the error below as it is an unmatched type with int
It is not possible to convert from int to "hdsportal.GestaoAlertas"
for this line:
GestaoAlertas _teste = addresses.Contains(ID);
And List<T>.Contains(T) returns the boolean result.
public bool Contains (T item);
Determines whether an element is in the List.
Solution(s)
Assumptions:
addresses contain GestaoAlertas records
You can get GestaoAlertas element from addresses by ID with either of these LINQ methods:
Pre-requisite:
Import System.Linq namespace.
using System.Linq;
Solution 1: Enumerable.SingleOrDefault
Returns a single, specific element of a sequence, or a default value if that element is not found.
Note: It will throw InvalidOperationException if input sequence contains more than one element. Good to be use for unique identifier such as ID.
GestaoAlertas _teste = addresses.SingleOrDefault(x => x.ID == ID);
Solution 2: Enumerable.Single
Returns a single, specific element of a sequence.
Note: It will throw ArgumentNullException if it returns null.
GestaoAlertas _teste = addresses.Single(x => x.ID == ID);
Solution 3: Enumerable.FirstOrDefault
Returns the first element of a sequence, or a default value if no element is found.
GestaoAlertas _teste = addresses.FirstOrDefault(x => x.ID == ID);
Solution 4: Enumerable.First
Returns the first element of a sequence.
Note: It will throw ArgumentNullException if it returns null.
GestaoAlertas _teste = addresses.First(x => x.ID == ID);

Get recent inserted Id and send in to another controllers view

EDITED:
I have Created CRUD Functions for each Modals and now i am trying to get recent Inserted Id and use it in different view.
Here is what I have tried so far
I have created 2 classes(Layer based) for CRUD function for each ContextEntities db to practice pure OOP recursive approach and following is the code.
1. Access Layer
ViolatorDB
public class ViolatorDB
{
private TPCAEntities db;
public ViolatorDB()
{
db = new TPCAEntities();
}
public IEnumerable<tbl_Violator> GetALL()
{
return db.tbl_Violator.ToList();
}
public tbl_Violator GetByID(int id)
{
return db.tbl_Violator.Find(id);
}
public void Insert(tbl_Violator Violator)
{
db.tbl_Violator.Add(Violator);
Save();
}
public void Delete(int id)
{
tbl_Violator Violator = db.tbl_Violator.Find(id);
db.tbl_Violator.Remove(Violator);
Save();
}
public void Update(tbl_Violator Violator)
{
db.Entry(Violator).State = EntityState.Modified;
Save();
}
public void Save()
{
db.SaveChanges();
}
}
2. Logic Layer
ViolatorBs
public class ViolatorBs
{
private ViolatorDB objDb;
public ViolatorBs()
{
objDb = new ViolatorDB();
}
public IEnumerable<tbl_Violator> GetALL()
{
return objDb.GetALL();
}
public tbl_Violator GetByID(int id)
{
return objDb.GetByID(id);
}
public void Insert(tbl_Violator Violator)
{
objDb.Insert(Violator);
}
public void Delete(int id)
{
objDb.Delete(id);
}
public void Update(tbl_Violator Vioaltor)
{
objDb.Update(Vioaltor);
}
}
And Finally using Logic Layer functions in presentation Layer.Here insertion is performed as:
public class CreateViolatorController : Controller
{
public TPCAEntities db = new TPCAEntities();
private ViolatorBs objBs;
public CreateViolatorController()
{
objBs = new ViolatorBs();
}
public ActionResult Index()
{
var voilator = new tbl_Violator();
voilator=db.tbl_Violator.Add(voilator);
ViewBag.id = voilator.VID;
return View();
}
[HttpPost]
public ActionResult Create(tbl_Violator Violator)
{
try
{
if (ModelState.IsValid)
{
objBs.Insert(Violator);
TempData["Msg"] = "Violator Created successfully";
return RedirectToAction("Index");
}
else
{
return View("Index");
}
}
catch (Exception ex)
{
TempData["Msg"] = "Failed..." + ex.Message + " " + ex.ToString();
return RedirectToAction("Index");
}
}
}
Now here is the main part how do i get perticuller inserted id in another controller named Dues while performing insertion ?
In sqlqery I would have used ##IDENTITY but in Entity Framework I'm not sure.
I'm new to mvc framework any suggestion or help is appreciated Thanks in Advance.
Once you save your db context the id is populated back to your entity by EF automatically.
for example.
using(var context = new DbContext())
{
var employee = new Employee(); //this has an id property
context.Employees.Add(employee);
context.SaveChanges();
var id = employee.id; // you will find the id here populated by EF
}
You dont need to add and save your table as you have done this already in your voilatorDB class just fetch the last id like following
var id = yourTableName.Id;
db.yourTableName.find(id);
Or you can simply write one line code to achive that using VoilatorBs class function
GetbyID(id);

ASP.Net vNext DbContext Dependency Injection multiple request issues.

I am attempting to use ASP.Net vNext, MVC, EF7, and the repository pattern (not the issue here, I don't think)...
The issue I'm having is that when multiple requests are made against the database, I'm getting the following error: "There is already an open DataReader associated with this Command which must be closed first."
Here's some code:
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
Configuration = new Configuration().AddJsonFile("config.json").AddEnvironmentVariables();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Register Entity Framework
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<MyDbContext>();
services.AddSingleton<ILocationRepo, LocationRepo>();
services.AddSingleton<IStateRepo, StateRepo>();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
var testData = ActivatorUtilities.CreateInstance<TestData>(app.ApplicationServices);
testData.InitializeData();
}
}
The controller:
[Route("api/[controller]")]
public class LocationsController : Controller
{
private readonly ILocationRepo _repo;
public LocationsController(ILocationRepo repo)
{
_repo = repo;
}
// GET: api/locations
[HttpGet]
public List<Location> Get()
{
return _repo.All.ToList();
}
// GET api/locations/5
[HttpGet("{id}")]
public IActionResult Get(int id)
{
var ret = _repo.GetById(id);
if (ret == null)
return new HttpNotFoundResult();
return new ObjectResult(ret);
}
// POST api/locations
[HttpPost]
public IActionResult Post([FromBody]Locationvalue)
{
var ret = _repo.AddOrUpdate(value);
if (ret == null)
return new BadRequestResult();
return new ObjectResult(ret);
}
// PUT api/locations/5
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody]Location value)
{
var ret = _repo.AddOrUpdate(value);
if (id == 0 || ret == null)
return new BadRequestResult();
return new ObjectResult(ret);
}
// DELETE api/locations/5
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var existing = _repo.GetById(id);
if (existing == null)
return new HttpNotFoundResult();
bool ret = _repo.TryDelete(id);
return new ObjectResult(ret);
}
}
The States repository:
public class StateRepo : IStateRepo
{
private readonly MyDbContext context;
public StateRepo(MyDbContext diContext)
{
context = diContext;
}
public IEnumerable<State> All
{
get
{
return context.States;
}
}
public State GetById(int id)
{
return context.States.FirstOrDefault(x => x.Id == id);
}
}
I have pretty much the same repo setup for Locations (with a few more methods, of course)... the problem comes in when I'm making simultaneous AJAX calls to my locations and states controllers. I would expect the DI for the context to handle such collisions, but it doesn't appear to be doing so. Is there another way to configure this to work correctly without having to go back to the old way of creating an instance of my context throughout my repos? Do I have anything configured incorrectly?
I don't claim to be a DI expert, but try registering your repositories with AddScoped instead of AddSingleton. I think you are getting the same instance of the repository for each request which probably has the same instance of your DbContext and DbContext is not thread safe.
Also, make sure you have MultipleActiveResultSets=true in your connectionstring. I think that can also cause the error you are seeing.

controller post actionresult not saving changes to database

I have a post method in my controller that is not saving changes to my database (SQL express). I am using viewmodels and valueinjector to populate the VM from my model. I have checked and the values in the viewmodel and they have changed, but when I call my service:
fixedAssetService.SaveFixedAsset()
and bookmark the following in the service interface:
unitOfWork.Commit()
and pull up the quick watch window for unitOfWork, it has the old value.
All my tables have primary keys and I am using code first. The connection string is valid becasue I can get the items, I just can't save them.
My post method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FixedAssetViewModel evm)
{
var fixedAsset = fixedAssetService.GetFixedAsset(evm.FixedAssetId);
// Use Injector to handle mapping between viewmodel and model
fixedAsset.InjectFrom(evm);
try
{
if (ModelState.IsValid)
{
fixedAssetService.SaveFixedAsset();
return RedirectToAction("Details", "FixedAsset", new { id = fixedAsset.FixedAssetId });
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
My Service:
namespace FixedAssets.Services
{
public interface IFixedAssetService
{
IEnumerable<FixedAsset> GetAll();
IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate);
FixedAsset GetFixedAsset(string id);
void CreateFixedAsset(FixedAsset fixedAsset);
void DeleteFixedAsset(string id);
void SaveFixedAsset();
bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate);
}
public class FixedAssetService : IFixedAssetService
{
private readonly IFixedAssetRepository fixedAssetRepository;
private readonly IUnitOfWork unitOfWork;
public FixedAssetService(IFixedAssetRepository fixedAssetRepository, IUnitOfWork unitOfWork)
{
this.fixedAssetRepository = fixedAssetRepository;
this.unitOfWork = unitOfWork;
}
#region IFixedAssetService Members
public IEnumerable<FixedAsset> GetAll()
{
var fixedAssets = fixedAssetRepository.GetAll();
return fixedAssets;
}
public IEnumerable<FixedAsset> FindBy(Expression<Func<FixedAsset, bool>> predicate)
{
IEnumerable<FixedAsset> query = fixedAssetRepository.FindBy(predicate);
return query;
}
public bool ValueInUse(Expression<Func<FixedAsset, bool>> predicate)
{
IQueryable<FixedAsset> query = fixedAssetRepository.FindBy(predicate).AsQueryable();
int count = query.Count();
return count > 0 ? true : false;
}
public FixedAsset GetFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
return fixedAsset;
}
public void CreateFixedAsset(FixedAsset fixedAsset)
{
fixedAssetRepository.Add(fixedAsset);
SaveFixedAsset();
}
public void DeleteFixedAsset(string id)
{
var fixedAsset = fixedAssetRepository.GetById(id);
fixedAssetRepository.Delete(fixedAsset);
SaveFixedAsset();
}
public void SaveFixedAsset()
{
unitOfWork.Commit();
}
#endregion
}
}
Edit: One thing I forgot to mention is this app was modeled almost exactly after an existing app that worked fine. Not sure if I have references messed up or what, but the other app uses the same methods only different entities
I found my problem. In the app I used as a model for this one I was using a separate unity class. My database factory registration was like this:
.RegisterType<IDatabaseFactory, DatabaseFactory>(new HttpContextLifetimeManager<IDatabaseFactory>())
Now I am using Microsoft.Practices.Unity and Unity.Mvc4 so I changed the registration to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>();
per the comments in the bootstrapper class. When I changed it to:
container.RegisterType<IDatabaseFactory, DatabaseFactory>(new HierarchicalLifetimeManager());
per the suggestions on this post:
Stackoverflow thread
it finally worked!

How can I use Entity Framework 6 and my repository to delete multiple records?

I am using Entity Framework 6 and I have a repository looking like the following with the Add and Update methods removed to make it shorter:
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
protected DbContext DbContext { get; set; }
protected DbSet<T> DbSet { get; set; }
public virtual IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return DbSet.Where<T>(predicate);
}
public virtual IQueryable<T> GetAll()
{
return DbSet;
}
public virtual T GetById(int id)
{
//return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id));
return DbSet.Find(id);
}
public virtual void Delete(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
public virtual void Delete(int id)
{
var entity = GetById(id);
if (entity == null) return; // not found; assume already deleted.
Delete(entity);
}
}
In my controller I call the repository like this:
public HttpResponseMessage DeleteTest(int id)
{
Test test = _uow.Tests.GetById(id);
if (test == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
try
{
_uow.Tests.Delete(test);
_uow.Commit();
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
This works for a single test but how can I delete for example all tests that have an examId column value of 1 being
that examId is one of the columns in the Test table.
You can create another delete method in your generic repository class, see below:
public virtual void Delete(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = DbSet.Where(predicate).AsQueryable();
foreach (T obj in query)
{
DbSet.Remove(obj);
}
}
Then you can use it like below, it will delete all records which Id equalsid.
_uow.Test.Delete(n => n.Id = id)
I'm not sure if EF is able to handle multiple delete now given a certain value, but the last time I did this I had to resort to a loop.
public HttpResponseMessage DeleteTest(int id)
{
var testList = _uow.Tests.GetAll().Where(o => o.Id == id);
if (testList.Count() == 0)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
try
{
foreach (var test in testList)
{
_uow.Tests.Delete(test);
}
_uow.Commit();
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
If the "test" table is a foreign table linked to a primary table on the "ID" column, you may want to consider doing a cascading delete in this case.
You can use RemoveRange()
public virtual void Delete(Expression<Func<T,bool>> predicate)
{
var query = Context.Set<T>().Where(predicate);
Context.Set<T>().RemoveRange(query);
}

Resources