I am working on a project that uses an MVC API with Entity Framework 6.1.3. We need to implement Get, GET/ID, Insert, Update and Delete API's.
for Insert and Update we have a parameter class that is not the same one as the DB, i am not sure what will be the best solution for that.
For an Update (the one that i have more questions) i can find a record and then update all properties manually, this is something i want to avoid. If i use the currentvalues from the entity then i will have to set the ExtraValues properties in all the apis that i am going to write, that kind of looks weird.
Note: i want to have a child class since most of the entities uses the same fields (Created/Updated) so instead of having those in all the classes i rather have them as inheritance.
There has to be a better solution for this problem, could someone help with ideas or best ways to do this.
public class DBClassA : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
public class DBClassB : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
//This will be use in all the classes
public class ExtraValues
{
public string SameValueInOtherClasses { get; set; }
public string SameValueInOtherClasses2 { get; set; }
public string SameValueInOtherClasses3 { get; set; }
}
[HttpGet]
public List<DBClassA> Get()
{
return new List<DBClassA>();
}
[HttpGet]
public DBClassA Get(int ID)
{
return new DBClassA();
}
[HttpPost]
public HttpResponseMessage Insert(DBClassA obj)
{
using (var context = new DBEntities())
{
context.Entity.Attach(DBClassA);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
[HttpPut]
public HttpResponseMessage Update(int ID, DBClassA obj)
{
using (var context = new DBEntities())
{
var entity = context.Entity.Find(ID);
//I will have to put the ExtraValues here
obj.ExtraValues = "";
_context.Entry(entity).CurrentValues.SetValues(obj);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
using (var db = new DBEntities())
{
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
}
more info: http://www.entityframeworktutorial.net/EntityFramework4.3/update-entity-using-dbcontext.aspx
Related
I need help with Entity Framework.
Controller:
public ActionResult Create([Bind(Prefix = "visit")] visit visit, [Bind(Prefix = "drugsEdition")] IEnumerable<drugsEdition> drugsEdition, [Bind(Prefix = "accessoryEdition")] IEnumerable<accessoryEdition> accessoryEdition, [Bind(Prefix = "servicesEdition")] IEnumerable<servicesEdition> servicesEdition)
{
Models.VisitDetails visitDetails = new Models.VisitDetails();
visitDetails.visit = visit;
if (ModelState.IsValid)
{
db.visit.Add(visit);
if (drugsEdition != null)
{
foreach (var item in drugsEdition)
{
item.idVisit = visit.id;
db.drugsEdition.Add(item);
}
}
if (accessoryEdition != null)
{
foreach (var item in accessoryEdition)
{
item.idVisit = visit.id;
db.accessoryEdition.Add(item);
}
}
if (servicesEdition != null)
{
foreach (var item in servicesEdition)
{
item.idVisit = visit.id;
db.servicesEdition.Add(item);
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = visit.id });
}
return View(visitDetails);
}
Model:
[Table("servicesEdition")]
public partial class servicesEdition
{
public int id { get; set; }
public int idService { get; set; }
public int idVisit { get; set; }
public double priceSell { get; set; }
[ForeignKey("idService")]
public virtual services services { get; set; }
}
In this code, I added a new visit to the database, and I want get the visit's id after the code line db.visit.Add (visit). When I add new drugsEdition and new accessoryEdition, this code is correct and added good idVisit, but when I addedservicesEdition idVisit = 0. Why 'servicesEdition' doesn't get good idVisit ?
If you add a virtual navigational property of type Visit, EF will get the corresponding visit id and fill that in when it saves a servicesEdition object on your SaveChanges method call.
I would also renaming the foreign key property to VisitId so that the referential integrity works by convention (naming convention) and you do not need to explicitly use the ForeignKey attribute. I would also recommend you to use PascalCasing when writing C# classes( not necessarily needed for your code to work)
public partial class ServicesEdition
{
public int id { get; set; }
public int idService { get; set; }
public int VisitId { get; set; }
public virtual Visit Visit { get; set; }
public double priceSell { get; set; }
[ForeignKey("idService")]
public virtual services services { get; set; }
}
If you do not want to alter the ServicesEdition entity definition like i described above, another option is to call the SaveChanges() method after you add the Visit entity and then you can access the Id property of that.
db.visit.Add(visit);
db.SaveChanges();
var visitId = db.Id;
//Now you can use visitId to save other entities
The issue is db.visit.Add(visit); does not generate id until hit db.SaveChanges();
You could add db.SaveChanges(); after db.visit.Add(visit);.
Or you will save as a graph like Shyju explained.
Right now, in smaller projects, I flounder between 3 approaches. I would like to choose one of them to use consistently.
I have put example code, with pros/cons below. But essentially, the 3 approaches boil down to:
All DB work in controller
DB instance passed from controller into ViewModel, and work done there
DB created in ViewModel with (using), and work done there
Why would an experienced developer choose one approach over another?
Are there any benefits / disadvantages to these approaches that - as a beginner - I might not be aware of?
Update
I appreciate that these patterns may not be used in larger projects which tend to me more structurally complex.
But really I am interested in the difference in them when they ARE used.
Approach One
All DB work and vaildation flow in controller
Makes it easy to grasp what's going on for simple applications...
...but 'fat' controllers may obscure understanding for complex applications.
Default used by MS in MVC template
public class ModelOne
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelOnePropAViewModel
{
public EditModelOnePropAViewModel(ModelOne model)
{
ID = model.ID;
PropA = model.PropA;
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerOne : Controller
{
private ApplicationDbContext DB = new ApplicationDbContext() { };
[HttpGet]
public ActionResult Edit(int id)
{
var model = DB.ModelOneDbSet.FirstOrDefault(i => i.ID);
var viewModel = new EditModelOnePropAViewModel(model);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelOnePropAViewModel postedModel)
{
if (ModelState.IsValid)
{
var model = DB.ModelOneDbSet.FirstOrDefault(i = i.ID == postedModel.ID);
model.PropA = postedModel.PropA;
DB.SaveChanges();
return RedirectToAction("index");
}
return View(postedModel);
}
}
Approach Two
DB passed into ViewModel from Controller
Controller is 'thin' and 'dumb'.
Because we are using the controller's DB instance, it's Dispose() method will be called on the DB object.
public class ModelTwo
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelTwoPropAViewModel
{
public EditModelTwoPropAViewModel(ApplicationDbContext db, int id)
{
ID = id;
PropA = db.ModelTwoDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
public void SaveChanges(ApplicationDbContext db)
{
var modelTwo = db.ModelTwoDbSet.FirstOrDefault(i => i.ID == ID);
modelTwo.PropA = PropA;
db.SaveChanges();
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerTwo : Controller
{
private ApplicationDbContext DB = new ApplicationDbContext() { };
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelTwoPropAViewModel(DB, id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelTwoPropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges(DB);
return RedirectToAction("index");
}
return View(postedModel);
}
}
Approach Three
Same as Two, but DB created with (using) in ViewModel.
public class ModelThree
{
public int ID { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
public string PropC { get; set; }
// etc...
}
public class EditModelThreePropAViewModel
{
public EditModelThreePropAViewModel(int id)
{
using (var db = new ApplicationDbContext())
{
ID = id;
PropA = db.ModelThreeDbSet
.Where(i => i.ID == id)
.Select(i => i.PropA)
.FirstOrDefault();
}
}
public void SaveChanges()
{
using (var db = new ApplicationDbContext())
{
var modelThree = db.ModelThreeDbSet.FirstOrDefault(i => i.ID == ID);
modelThree.PropA = PropA;
db.SaveChanges();
}
}
public string PropA { get; set; }
public int ID { get; set; }
}
public class ControllerThree : Controller
{
[HttpGet]
public ActionResult Edit(int id)
{
var viewModel = new EditModelThreePropAViewModel(id);
return View(viewModel);
}
[HttpPost]
public ActionResult Edit(EditModelThreePropAViewModel postedModel)
{
if (ModelState.IsValid)
{
postedModel.SaveChanges();
return RedirectToAction("index");
}
return View(postedModel);
}
}
Your question is too vast and includes lots of different aspects. So I try to give you a very general view. There is nothing wrong or right in software design. There are "Project needs and specifications". But generally it has been accepted using "Design Patterns and Principles" makes project more robust,reliable and easy to maintain and make your code "loosely coupled and highly cohesive".
Having these in your mind, I give my opinion for your selections:
All DB work in controller: This is good for small project that possibility for its expansion in future is low. It is easy to setup and easy to understand for other developers who join later. It is not good for project that need to change database later, which in that case you need to rewrite all your methods and probably your views. Another weakness of this approach is repetitive code for every object you need to add.
DB instance passed from controller into ViewModel, and work done there: For my understanding from you code, you tried add a layer for your project to have all database actions there. It is good if you need to change your data,database or preventing from repetitive code. If you want that, it is better to use another project in you solution,known as "Multi tier architect ", to have more flexibility and "loosely coupled" sections. In that case you could easily replace your database project with other one later without worrying about your views and controllers, in multi tier architect knows as presentation layer.
DB created in ViewModel with (using), and work done there: This isn't new approach. Having "Using block" in your code is about "thread starvation" (more information here).
At the end, consider your project needs and specifications, design your architecture and follow your architecture based on "Design Patterns and Principles". For more information about Design Patterns, I recommend the book "Professional ASP.NET Design Patterns" from "Scott Millett".
I've built my Domain model layer, my repository layer, and now I'm working on my DTO layer to be used by a webApi project. I'm in the middle of implementing an Update service method, and I'm wondering about partial updates. Here's my DTO class:
public class FullPersonDto
{
public FullPersonDto()
{
Friends = new List<Person>();
}
public FullPersonDto(Person person)
{
PersonId = person.PersonId;
DateCreated = person.DateCreated;
Details = person.Details;
Friends = new List<Person>();
foreach (Person friend in person.Friends)
{
Friends.Add(new PersonDto(friend));
}
}
[Key]
public int PersonId { get; set; }
[Required]
public virtual DateTime DateCreated { get; set; }
public virtual string Details { get; set; }
public List<Person> Friends { get; set; }
public Person ToEntity()
{
var person = new Person
{
PersonId = PersonId,
DateCreated = (DateTime) DateCreated,
Details = Details,
Friends = new List<Person>()
};
foreach (PersonDto friend in Friends)
{
person.Friends.Add(friend.ToEntity());
}
return person;
}
}
Here's my Update method in my Repository:
public Person UpdatePerson(Person person)
{
var entry = _db.Entry(person);
if (entry.State == EntityState.Detached)
{
var dbSet = _db.Set<Person>();
Person attachedPerson = dbSet.Find(person.PersonId);
if (attachedPerson != null)
{
var attachedEntry = _db.Entry(attachedPerson);
attachedEntry.CurrentValues.SetValues(person); // what if values are null, like ID, or DateCreated?
}
else
{
entry.State = EntityState.Modified;
}
}
SaveChanges();
return person;
}
My question is: What if I only need to update the Details of a person via my webAPI? Is the convention to construct an entire PersonDto and Update the entire object using SetValues, or is there any way I can specify that I only want a single field updated so that I don't have to send a ton of data over the wire (that I don't really need)?
If it is possible to do partial updates, when is it ever good to update the entire entity? Even if I have to update 5/7 properties, it requires that I send old data for 2/7 to re-write so that SetValues doesn't write nulls into my fields from my DTO.
Any help here would be awesome... totally new to this stuff and trying to learn everything right. Thank you.
I've taken similar approach to do optimization, and I've faced same issues with null values when attaching (not just null, you'll have issue with boolean as well). This is what I've come up with:
public static void Update<T>(this DbContext context, IDTO dto)
where T : class, IEntity
{
T TEntity = context.Set<T>().Local.FirstOrDefault(x => x.Id == dto.Id);
if (TEntity == null)
{
TEntity = context.Set<T>().Create();
TEntity.Id = dto.Id;
context.Set<T>().Attach(TEntity);
}
context.Entry(TEntity).CurrentValues.SetValues(dto);
var attribute = dto.GetAttribute<EnsureUpdatedAttribute>();
if (attribute != null)
{
foreach (var property in attribute.Properties)
context.Entry(TEntity).Property(property).IsModified = true;
}
}
That is extension method for DbContext. Here are the interfaces IDTO and IEntity:
public interface IDTO
{
int Id { get; set; }
}
public interface IEntity
{
int Id { get; set; }
Nullable<DateTime> Modified { get; set; }
Nullable<DateTime> Created { get; set; }
}
I'm using my custom EnsureUpdatedAttribute to annotate what properties should always be updated (to deal with nulls / default values not being tracked):
public class EnsureUpdatedAttribute : Attribute
{
public IEnumerable<string> Properties { get; private set; }
public EnsureUpdatedAttribute(params string[] properties)
{
Properties = properties.AsEnumerable();
}
}
And this is a sample of usage:
public class Sample : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public Nullable<DateTime> Modified { get; set; }
public Nullable<DateTime> Created { get; set; }
}
[EnsureUpdated("Active")] /// requirement for entity framework change tracking, read about stub entities
public class SampleDTO : IDTO
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[JsonIgnore] /// How to exclude property from going on the wire / ignored for serialization
public bool Active { get; set; }
}
[HttpPost]
public HttpResponseMessage SaveSample(SampleDTO dto)
{
dto.Active = true;
_ctx.AddModel<Sample>(dto);
_ctx.SaveChanges();
return NoContent();
}
return NoContent() is just extension for returning 204 (NoContent).
Hope this helps.
Theres a few options you have, you can create a stored procedure to update the required parts (I wouldnt do this), or you can manually select the fileds to update on the model before saving the context changes with EF.
Heres an example how to update a specific field:
public void UpdatePerson(int personId, string details)
{
var person = new Person() { Id = personId, Details = details };
db.Persons.Attach(personId);
db.Entry(person).Property(x => x.Details).IsModified = true;
db.SaveChanges();
}
It will depend on your scenario what you want to do, but generally speaking its fine to send your whole entity to be updated, and this is how i would approach your situation potentially changing in the future if needed.
I have this 4 models - 2 Domain Models and 2 DTOs
public class Project
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public virtual int ProjectID { get; set; }
public string Name { get; set; }
public virtual Project Project { get; set; }
}
public class ProjectDTO
{
[Required]
public string Name { get; set; }
public List<TaskDTO> Tasks { get; set; }
}
public class TaskDTO
{
[Required]
public string Name { get; set; }
public int ID { get; set; }
public bool MarkRemove { get; set; }
}
Heres my automapper configuration
Mapper.CreateMap<Project, ProjectDTO>();
Mapper.CreateMap<ProjectDTO, Project>().ForMember(p =>p.ID, opt=>opt.Ignore()).ForMember(p=>p.Tasks, opt=>opt.Ignore());
Mapper.CreateMap<Task, TaskDTO>();
Mapper.CreateMap<TaskDTO, Task>().ForMember(task=>task.ProjectID, opt=>opt.Ignore()).ForMember(task=>task.Project, opt=>opt.Ignore());
Heres my HttpPost Edit action
[HttpPost]
public ActionResult Edit(int id, ProjectDTO p)
{
if (ModelState.IsValid)
{
var dbProject = db.Projects.Where(pr => pr.ID == id).Single();
Mapper.Map(p, dbProject);
foreach (var task in p.Tasks)
{
Task dbTask;
try
{
dbTask = dbProject.Tasks.Where(t => t.ID == task.ID).Single();
}
catch
{
dbTask = new Task();
Mapper.Map(task, dbTask);
dbProject.Tasks.Add(dbTask);
}
if (task.MarkRemove)
{
db.Tasks.Remove(dbTask);
}
else {
Mapper.Map(task, dbTask);
}
}
db.Entry(dbProject).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(p);
}
Im not completely happy with this but I dont think there is a much cleaner approach to handling this somewhat complex scenario....
now that it is working I would like to at least refactor this to use repository pattern or something in a way that the controller action is not that convoluted.. this will eventually be production code :s
can anyone give me some advice on how to refactor this?
please help.
I would use a service layer, like this:
public interface IProjectsService
{
void RemoveTasks(int projectId, IEnumerable<int> taskIdsToRemove);
}
and then the controller would depend on this service layer:
public class ProjectsController : Controller
{
private readonly IProjectsService _service;
public ProjectsController(IProjectsService service)
{
_service = service;
}
public ActionResult Edit(int id)
{
// TODO: Add methods to your service layer
// allowing to retrieve projects, then map
// the resulting project into a view model
throw new NotImplementedException();
}
[HttpPost]
public ActionResult Edit(int id, ProjectDTO p)
{
if (!ModelState.IsValid)
{
return View(p);
}
var taskIdsToRemove = p.Tasks.Where(x => x.MarkRemove).Select(x => x.ID);
_service.RemoveTasks(id, taskIdsToRemove);
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
}
This way the controller logic is more weakly coupled to the way we do data access. That's an implementation detail that a controller should never have to worry about.
As a further improvement to the RemoveTasks method you could make it return a boolean indicate the success or failure of the operation along with an error message so that the Edit action could redisplay the view and show the error in case something goes wrong.
Now as far as this service layer is concerned, the RemoveTasks method is a business operation that could be built upon multiple CRUD operations with some repository. So this service layer would itself depend on a repository. It is only this repository that will have to know about EF or whatever you are using to do your data access.
So basically everytime I see someone asking a question about ASP.NET MVC and EF at the same time, for me, those are two completely different questions. ASP.NET MVC should not know anything about EF. EF should be buried away behind an abstraction of a repository.
I realize this question has been inactive for a while, but I had the same problem, and though I would post my solution for anyone else struggling with the nested model not being saved during edit.
[HttpPost]
public ActionResult Edit(ProjectDTO p)
{
if (ModelState.IsValid)
{
// *****MODIFIED CODE HERE********
for (int i = 0; i < p.Tasks.Count; i++)
{
db.Entry(p.Tasks[i]).State = EntityState.Modified;
}
// *************************************
db.Entry(dbProject).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(p);
}
Basically, you want to set State of each nested model to Modified as well as the root model.
Every time I add a new App It creates a new AppCategory. I am seriously screwing this up somehow
code first entity framework objects
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<App> apps { get; set; }
}
public class App
{
public int ID { get; set; }
public string Name { get; set; }
public AppCategory Category { get; set; }
}
Editor Template (I would love to just make just one Foreign Key EditorTemplate)
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("Category", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
and of course the repository
public static IEnumerable<SelectListItem> GetAppCategoriesSelect()
{
return (from p in GetAppCategories()
select new SelectListItem
{
Text = p.Name,
Value = p.ID.ToString(),
});
}
public static ICollection<AppCategory> GetAppCategories()
{
var context = new LIGDataContext();
return context.AppCategories.ToList();
}
Every time I add a new App It creates a new AppCategory I am seriously screwing this up somehow
Adding more debug info
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
gives me a validation message on the post
Parameters application/x-www-form-urlencoded
Category 1
Name 8
Validation error The value '1' is invalid.
This makes sense because Category should be an object not an integer.
Controller Code as asked for
pretty sure this isnt the problem as it came from MVCScaffold
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
My model was incorrectly set up ... virtual ICollection and just the foreign key id for the sub and everything worked... changes below
Model
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public **virtual** ICollection<App> Apps { get; set; }
}
public class App
{
public int ID { get; set; }
********************************************
[UIHint("AppCategory")]
public int AppCategoryID { get; set; }
********************************************
public string Name { get; set; }
}
public class LIGDataContext : DbContext
{
public DbSet<AppCategory> AppCategories { get; set; }
public DbSet<App> Apps { get; set; }
}
/Views/Shared/EditorTemplates/AppCategory.cshtml
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
AppController
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
this.repository.Add(d);
this.repository.Save();
return RedirectToAction("Index");
}
return View();
}
If you bind your dropDownList to Category.Id, you'll at least get the selected value into that filed, but nothing else in your Category Object.
The model binder cannot create the AppCategory object from the form collection in your Create action because the form only has an ID for that object (the other properties of AppCategory are not there).
The quickest solution would be setting the Category property of your App object manually, like this :
[HttpPost]
public ActionResult Create(App d) {
int categoryId = 0;
if (!int.TryParse(Request.Form["Category"] ?? String.Empty, out categoryId) {
// the posted category ID is not valid
ModelState.AddModelError("Category",
"Please select a valid app category.")
} else {
// I'm assuming there's a method to get an AppCategory by ID.
AppCategory c = context.GetAppCategory(categoryID);
if (c == null) {
// couldn't find the AppCategory with the given ID.
ModelState.AddModelError("Category",
"The selected app category does not exist.")
} else {
// set the category of the new App.
d.Category = c;
}
}
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}