Cannot save changes to Database Context - c#

I have a problem saving the changes to Database Context. When i don't save i can see that the listing status is successfully changed , but when i try to save it I get an error which is saying : " There is already an open DataReader associated with this Connection which must be closed first." And i don't know where that comes from. When i try to do it asynchronous i get the same error.
AdministratorController.cs
[Route("/Admin")]
[ApiController]
public class AdministratorController : Controller
{
private readonly dbContext _dbContext;
public AdministratorController(dbContext dbContext)
{
_dbContext = dbContext;
}
///////////////////////////////////
/// //
/// Accept or Reject Listings //
/// //
//////////////////////////////////
[HttpPost]
[Route("acceptListing/{listingId}")]
[AllowAnonymous]
//[Authorize(Roles="Administrator")]
public ActionResult AcceptList([FromRoute]int listingId)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
if (listingId == null)
{
return NotFound("Listing not found.");
}
foreach (Listing listing in _dbContext.Listings)
{
Console.WriteLine(listing.Status);
if(listing.Id == listingId)
{
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
}
return Ok();
}
}
return BadRequest("Couldn't find right listing.");
}

Rather than looping through all listings looking for the one with the Id you want, just filter and get.
Listing? listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);
if (listing is null)
{
return BadRequest("Couldn't find right listing.");
}
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
return Ok();

The problem here is that you are iterating the data being fetched from database and in the same time you are trying to save something from the same context. Quick fix is to use ToList in foreach:
foreach (Listing listing in _dbContext.Listings.ToList())
{
// ..
}
But in general you should not fetch everything from the database to process only one item. Just write query that will filter everything on database side. Something along this lines (not tested):
var listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);
if (listing is null)
{
return NotFound();
}
if (listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.SaveChanges();
}
return Ok();

change the code like this :
var listings = _dbContext.Listings.Tolist();
foreach (Listing listing in listings)
{
Console.WriteLine(listing.Status);
if(listing.Id == listingId)
{
if(listing.Status == ListingStatus.Accepted)
{
return BadRequest("Listing already accepted.");
}
else
{
listing.Status = ListingStatus.Accepted;
Console.WriteLine(listing.Status);
_dbContext.Update(listing);
_dbContext.SaveChanges();
}
return Ok();
}
}

Related

Business logic in ASP.NET Core MVC

This is my first question here so I'm really open for opinions, I searched a lot about ASP.NET Core MVC and still I don't have enough answers if I'm writing code in right way.
In many tutorials on Youtube I saw people create ASP.NET Core MVC applications with CRUD operations but there were just simple without any logic and all code was in controllers.
What if I want add some logic for example checking if my record already exists in the database? Where should I put this?
I have class Patient and I want add patient to database so I got in patient controller this :
public async Task<IActionResult> Create([Bind("PatientId,Name,Surname,Pesel")] Patient patient)
{
if (ModelState.IsValid)
{
String result = facade.Add_patient(patient);
if (result == "Patient added")
{
_context.Add(patient);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
{
ViewBag.Message = "Patient exists";
return View();
}
}
}
Facade is my class in model folder where I have this :
public class Facade
{
private Database_controller _context;
public Facade(Database_controller context)
{
_context = context;
}
private List<Patient> patients = new List<Patient>();
public List<Patient> Patients { get => patients; set => patients = value; }
public void updatedata()
{
patients = _context.Patients.ToList();
}
public string Add_patient(Patient patient)
{
Patient Patient = new Patient();
Patient.Name = patient.Name;
Patient.Surname = patient.Surname;
Patient.Pesel = patient.Pesel;
String if_is = addpacjent(Patient);
if (!"Is".Equals(if_is))
{
return "Patient added";
}
else
{
return "Patient exists";
}
}
public String addpacjent(Patient val)
{
bool if_is = patients.Contains(val);
if (if_is == true)
{
return ("Is");
}
else
{
patients.Add(val);
return null;
}
}
}
In Patient class is override method equals for checking
public override bool Equals(Object ob)
{
String Name = Surname;
String Name2 = ((Patient)ob).Surname;
String Pesel1 = Pesel;
String Pesel2 = ((Patient)ob).Pesel;
bool a = Name.Equals(Name2);
if (Pesel2 != "0")
{
bool b = Pesel1.Equals(Pesel2);
bool c = false;
if (a && b == true)
{
c = true;
}
return c;
}
else
return a;
}
Is it the right way? Should I have method _context.Add(patient) in Facade or controller? Where should I check existence in database?
I already have application which I want write in .NET Core in Winforms so I want use as many as possible code from Winforms classes so it's why I started coding like this in ASP.NET Core MVC
The easiest way is to use Any(),this is because Any() will return as soon as it finds a match.
Like my following:
public IActionResult Test()
{
//Simulation data:
var patient = new Patient
{
Name="AA",
SumName="AA-aa",
Pesel="23"
};
//Here you can add your conditions.
if (!_context.Patients.Any(o => o.Name == patient.Name&&o.SumName==patient.SumName&&o.Pesel==patient.Pesel))
{
_context.Add(patient);
_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
else
{
ViewBag.Message = "Patient exists";
return View();
};
}
Below is the demo data in my database:
Sample effect display:

Is there a way to search other than by primary key

I'm coding an asp.net web api which will access data held on a mysql database. I am able to search for specific details through 'api/company/{company id number} which shows its working.
However what I'm wondering is whether there is a way to return a result based on {company name} rather than the primary key {company id}.
My reason for this is that the search query is more likely to be based on trying to find a company record by name rather than by company id.
Sorry if i sound like a noob, that's because I am.
I've tried changing (int id) to (string id) but that simply throws an error as its searching through the primary key field which is type int.
public class companyController : ApiController
{
private apollodbEF db = new apollodbEF();
// GET: api/company
public IQueryable<companydetail> Getcompanydetails()
{
return db.companydetails;
}
// GET: api/company/5
[ResponseType(typeof(companydetail))]
public IHttpActionResult Getcompanydetail(int id)
{
companydetail companydetail = db.companydetails.Find(id);
if (companydetail == null)
{
return NotFound();
}
return Ok(companydetail);
}
public class companyController : ApiController
{
private apollodbEF db = new apollodbEF();
// GET: api/company
public IQueryable<companydetail> Getcompanydetails()
{
return db.companydetails;
}
// GET: api/company/5
[ResponseType(typeof(companydetail))]
public IHttpActionResult Getcompanydetail(string id)
{
companydetail companydetail = db.companydetails.Find(id);
if (companydetail == null)
{
return NotFound();
}
return Ok(companydetail);
}
You can use a where statement
var company = db.companydetails.Where(c => c.Name == "My Company").FirstOrDefault();

ASP.net MVC Sharing methods

I have two methods that use different viewmodels but are the same logic. At the moment I have copied and pasted them into their respective controllers. Any way to share these methods somehow?
Song Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
PublishedSongViewModel song = db.PublishedSongs.Single(x => x.Id == id);
song.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Station Controller:
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
RadioStationViewModel station = db.RadioStations.Single(x => x.Id == id);
station.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = station.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
Edit:
class so far:
public static IEnumerable<Type> GetElements(ApplicationDbContext db, Type type)
{
if (type == typeof(SongsController))
return (IEnumerable<Type>)db.PublishedSongs;
else if (type == typeof(RadioStationsController))
return (IEnumerable<Type>)db.RadioStations;
else
throw new Exception("Controller not found, DBHelper");
}
Create a class called BasicController and add the method to it, like this:
public class BasicController {
public JsonResult IncrementViews(int id)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
var element = DBHelper.GetElements(db, this.GetType()).Single(x => x.Id == id);
element.UniquePlayCounts++;
db.SaveChanges();
return Json(new { UniquePlayCounts = song.UniquePlayCounts }, JsonRequestBehavior.AllowGet);
}
}
}
and modify your classes to inherit from BasicController. You will also have to create the DBHelper class with the GetElements method, which gathers the IEnumerable elements from db based on type.
EDIT: This is how you can create a helper:
public class DBHelper {
public static IEnumerable GetElements(ApplicationDbContext db, System.Type type) {
if (type == typeof(SongController)) {
return db.PublishedSongs;
} else if (type == typeof(StationController)) {
return db.RadioStations;
}
}
}

Entity Framework - Many to Many relationship not saving to database

I have stumbled upon a problem with Entity Framework this morning.
I have following code mapping a modified entity and saving it into database.
public Group Save(Group x)
{
using (var db = new HostContext())
{
db.Projects.Attach(x.Project);
if (x.ID != 0)
{
db.AttachableObjects.Attach(x);
var manager = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager;
manager.ChangeObjectState(x, EntityState.Modified);
}
else
{
db.AttachableObjects.Add(x);
}
db.SaveChanges();
return x;
}
}
I call Save method with existing group as a parameter. Group contains one user I want to add as a member.
The method finishes successfully, however the relationship is not persisted in database.
Any help is very appreciated.
EDIT: These are my classes
class User : AttachableObject
{
...
private List<Group> memberof;
[DataMember]
[InverseProperty("Members")]
public List<Group> MemberOf
{
get { return memberof; }
set { memberof = value; }
}
...
}
class Group : AttachableObject
{
...
private List<User> members;
[DataMember]
[InverseProperty("MemberOf")]
public List<User> Members
{
get { return members; }
set { members = value; }
}
...
}
EDIT2: This is where the Save method is called
public Group AcceptInvite(int id)
{
var mapper = new InviteMapper();
var userMapper = new UserMapper();
var groupMapper = new GroupMapper();
var invite = mapper.Find(id);
if (invite != null)
{
var group = groupMapper.Find(invite.GroupID);
var user = userMapper.Find(invite.InviteeID);
group.Members.Add(user);
mapper.Delete(invite.ID);
return groupMapper.Save(group);
}
return null;
}
EDIT3: My mappers
public class GroupMapper
{
public Group Find(int id)
{
using (var db = new HostContext())
{
return db.AttachableObjects
.Include("Project")
.OfType<Group>().FirstOrDefault(x => x.ID == id);
}
}
}
The rest of the mappers is the same, only using their own tables.
You are not changing the relationship info of Project, you are only setting x to modified, relationship info must be changed explicitly.
So x.Project must have some property that points back to Group, you need to set it so the change is recorded.
I am guessing that x is resurrected via some deserialization process?

How to implement Insert, Update and Delete in a parent class that works for all database models using sqlite-net?

I'm using a SQLite database in a Windows 8 Store Application.
Furthermore I use the SQLite wrapper sqlite-net to do operations on the database.
I will explain this in detail so that you understand my situation.
There are Model classes that contain the business logic, for example the class Location. Then there are DatabaseModels from which the Database Tables are generated, for example LocationDb.
I don't want to implement the basic database methods like Insert(Model model), Update(Model model), Delete(int id), GetAllRecords() and GetRecord(int id) in every Model class. Instead I want to implement these methods in a base class of all models. This base class of all models is called ModelBase; the base class of all database Models is DbModelBase.
There is no problem implementing the Insert method as follows for all models:
public int Insert(ModelBase model)
{
int id;
using (var db = new SQLite.SQLiteConnection(MainApplication.DBPath))
{
db.Insert(model.GetDbModelFromModel());
id = (int)SQLite3.LastInsertRowid(db.Handle);
}
return id;
}
But I don't know how to implement the other ones using sqlite-net.
I need to find a specific data record in a specific table. I have a model object which contains the id. But how to make one method work with all model classes without explicitly calling the specific table?
The following code works for a single specific database table...
using (var db = new SQLite.SQLiteConnection(MainApplication.DBPath))
{
var queryResult = db.Table<LocationDb>().Where(l => l.Id == model.Id);
if (queryResult.Count() == 1)
{
db.Update(new TeacherDb(model));
}
}
... but I can not write
var queryResult = db.Table<typeof(databaseModel)>().Where(t => t.Id == model.Id);
Write a generic function for that.
What about this solution:
public class BaseModel<T> where T : Type
{
protected T Id;
/// <summary>
/// Adds a new record to data base. It doesn't check for record existance.
/// </summary>
/// <returns></returns>
protected BaseModel<T> Add()
{
try
{
var entities = Variable.CurrentEntities;
var dbSet = entities.Set<BaseModel<T>>();
var result = dbSet.Add(this);
entities.SaveChanges();
return result;
}
catch (Exception exception)
{
LogException.HandleException(exception);
return null;
}
}
protected bool Update()
{
try
{
var entities = Variable.CurrentEntities;
var dbSet = entities.Set<BaseModel<T>>();
var original = dbSet.Find(this);
entities.Entry(original).CurrentValues.SetValues(this);
entities.SaveChanges();
return true;
}
catch (Exception exception)
{
LogException.HandleException(exception);
return false;
}
}
protected BaseModel<T> Delete()
{
try
{
var entities = Variable.CurrentEntities;
var dbSet = entities.Set<BaseModel<T>>();
var result = dbSet.Remove(this);
entities.SaveChanges();
return result;
}
catch (Exception exception)
{
LogException.HandleException(exception);
return null;
}
}
protected bool Upsert()
{
try
{
var entities = Variable.CurrentEntities;
var dbSet = entities.Set<BaseModel<T>>();
dbSet.AddOrUpdate(this);
return true;
}
catch (Exception exception)
{
LogException.HandleException(exception);
return false;
}
}
protected BaseModel<T> Save()
{
try
{
var entities = Variable.CurrentEntities;
var dbSet = entities.Set<BaseModel<T>>();
var original = dbSet.Find(Id);
if (original == null)
{
original = dbSet.Add(this);
}
else
{
entities.Entry(original).CurrentValues.SetValues(this);
}
entities.SaveChanges();
return original;
}
catch (Exception exception)
{
LogException.HandleException(exception);
return null;
}
}
}

Categories