In an ASP.NET MVC application using entity framework, I have an Edit view where I am updating some properties on one of my entity models. I'd like to put an "add" button for one of the properties if the user wants to add a new property of that type (but without deleting the older entry, I understand this requires a List of those type of properties). When I click this add button, using Ajax.ActionLink will retrieve a new Html.EditorFor helper from a partial view and add it to the current form.
Currently my ActionMethod looks like it's only updating the fields that are already there, what kind of logic can I add to say "if this is a new property, then db.Add"?
heres what my method looks like now:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TimeSeriesData(List<TimeSeriesData> List1)
{
int empid = List1[0].EmployeeID;
if (ModelState.IsValid)
{
foreach (TimeSeriesData ln in List1)
{
db.Entry(ln).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Edit", new { id = empid });
}
return new EmptyResult();
}
I'd like to add logic somewhere inside the foreach loop and say if the item in the list is a new item, then db.MyModel.List.Add(item); but I'm not sure how to check if the list item is new
would it be something like
if (newproperty.propertyid == null) {
db.MyModel.List.Add(newproperty);
}
Please let me know if you need more clarification
EDIT
Or another way to ask my question is: how can I check if the item already exists in the database,
models:
//summarized
class Employee {
int EmployeeID {get;set;}
List<TimeSeriesData> TimeSeriesData {get;set;}
}
class TimeSeriesData {
int TimeSeriesDataID {get; set;}
int EmployeeID {get;set;}
string Value {get;set;}
[ForeignKey("EmployeeID")]
public Employee Employee { get; set; }
}
There's a problem within your code structure. If you will notice,
db.Entry(ln).State = EntityState.Modified;
you are just updating/editing that specific entity and not adding a property. And also, I assume that you kinda missed out some logic.
If you're going to add another property for it, you should put it in a separate table and reference it to the primary key of the parent table. By this, you will not have any problem adding another value for that.
To answer your second question, you can use .Any property of the context to check out if the item exists in the database. You can have something like this:
var db = new Context();
if(!db.mytable.Any(m => m.field == value)){
db.mytable.Add(value);
db.SaveChanges();
}
Should you have any question, please feel free to ask.
Related
I'm having trouble updating an item on my database. I've tried several different ways, but nothing seems to work. Here is my latest attempt:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
When SaveChanges executes, I get the following error message: "The instance of entity type ItemStatus cannot be tracked because another instance with the key value ['ItemStatusId: 4'] is already being tracked.
Here are the relevant properties of my Item model:
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemId { get; set; }
public ItemStatus InitialStatus { get; set; }
public ItemStatus FinalStatus {get; set; }
}
And here are the relevant properties from the ItemStatus class:
public class ItemStatus
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ItemStatusId { get; set; }
public string ItemStatusName{ get; set; }
]
Can anyone tell me what I'm doing wrong? I have checked to make sure I don't have an undisposed context somewhere. Also, when I look at the change tracker, I can see that it is tracking an entry from each row on the ItemStatus table. This doesn't seem right. Shouldn't it only track the ItemStatus that has been assigned to localItem rather than all the related entities?
I am calling the method from here:
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
The various properties of ItemToDisplay are bound to dropdown lists in the UI. I have verified that these properties are being assigned correctly before being sent to the InsertOrUpdateFindingAsync method.
I have also tried a this for the InsertOrUpdate method:
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
Item itemFromDb = context.Items.Where(i => i.ItemId == localItem.ItemId).FirstOrDefault();
itemsFromDb = localItem;
context.Items.Update(localItem);
context.SaveChanges();
}
}
You apparently save a localItem that refers to two ItemStatus objects both having ItemStatusId = 4. The cause of the error is that these ItemStatus objects are two instances instead of one. The Update command tries to attach both of these instances to the context.
There are two ways to fix this:
Make sure that if both statuses in Item are identical they both refer to the same ItemStatus object. Depending on where localItem comes from, this may require JSON serializer settings or a modification in the code that constructs the localItem object.
(Preferred) Add the primitive foreign properties InitialStatusId and FinalStatusId to Item and only set these properties, not the references. Then the update is a simple update of scalar properties.
Because
savedItem = awaitdataService.InsertOrUpdateFindingAsync(ItemToDisplay);
this variable is itemtodisplay, you have to read from once and you keep this data in this variable, so your context to database, tracked this record when you read.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
context.Items.Update(localItem);
context.SaveChanges();
}
}
Then here when you sent this variable to there, there is already tracked variable, you tried to track it one more time.
There are 2 solutions:
Read this record with .AsNoTracking so it will not be tracked
When your update method you need to read one more time, like searching with id and keep it in another variable, and assign the parameters which you want to update to sent variable to new read one. then save it so it will not tracked, because there is a new read, actually you read this with tracking parameter because you don't need to add .AsNoTracking here, but it will create a problem because you are trying to change something on same tracked context.
I will add basic code based on my 2. desc.
public async Task<Finding> InsertOrUpdateItemAsync(Item localItem)
{
using (var context = new AppDbContext())
{
var data = context.Items.Where(x => x.Id == localItem.Id).FirstOrDefault();
data.Name = localItem.Name;
context.Items.Update(data);
context.SaveChanges();
}
}
I hope I described it well.
But note: using update method is not the best, because in that example I only want to update name property but it will create query that updates all columns. You should do it with out update, else there is 2 option here, if write update, it says hey EF mark all need to update, so it creates a query that to update all columsn. But this data is already tracked, if you change name like example EF Core knows it so you should only write there, SaveChanges(); or second method is if you want to update only one record, but your record is read with .AsNoTracking, you should access the context, get entries and change the field as modified
public void ContextAttach(TEntity entity)
{
_dataContext.Attach(entity);
_dataContext.Entry(entity).Property(Name).IsModified = true;
}
https://blog.oneunicorn.com/2020/01/17/dontcallupdate/
So, consider the following class:
public class Solution
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And the following methods
public void DoSomething()
{
new Item originalItem = new Item();
item.Name = "Test";
InserIntoDb(originalItem);
Assert.True(item.Id != 0);
}
public void InserIntoDb(Item item)
{
context.Item.Add(item);
context.SaveChanges();
}
On this case, after insert into the db, EF automatically updates the originalItem with its auto generated ID value.
My problem starts when I add some logic to only add to the db if the Name doesn't exist. If exists the Insert doesn't happen therefore the ID property is not populated.
My question is: Is there a way to make the context.Item (when retrieving all) to automatically update the ID of the originalItem value without having to change the InsertIntoDb method to return an Item and consequently having to add a line like
originalItem = InsertIntoDb(originalItem);
Thanks, in advance
Nope, there is no built in way to update the item with the id from the db if it exists.
You will have to implement a custom approach to make this work. If this is only a small or single amount of items at once, then just attempt to get the named item from the database prior to entry and then for the one(s) not present insert.
If you are doing this for a large batch of items (more than 5000), consider using a stored procedure to do this work on the db server side for query performance.
I got one question related to my model you can see in the picture below.
As you can see I got 3 entities and 1:n and m:n relations between them.
I want that I can edit these models through a web interface. Therefore I scaffold (add controller with entity framework) these three models and got edit/delete/create/ views and of course one controller for each entity.
But there is no input/fields created for the relations automatically by VS. So I thought to implement them manually. Before I want to do that is there an simpler way to implement/scaffold this model, so I can even edit the relations(Checkboxes or (multi)select would be the best)?
Thanks in advance!
For one-many you can use a DropDownList for Tip in the Partner View (see Scott Allen's solution. Many-many can be handled by ViewModels and JavaScript frameworks like Knockout.
No, the scaffolds are intentionally unopinionated here, as there's many different ways you could handle this. Perhaps you just want to choose from a select list? Maybe you want checkboxes, instead? Or, maybe you want to actually add/edit related items inline? And with that last one, would you like to post all at once or use AJAX?
So, instead of picking for you, the framework rightly leaves the decision up to you, since only you know how your application should be built. Regardless, relying on the scaffolds is going to bite you more often than not. They only work in the most basic and ideal scenarios, and when have application requirements ever been either basic or ideal? I don't even bother with them at this point, preferring to just create my controllers/views manually. It ends up being quicker than dealing with the scaffold and undoing all the things that aren't applicable.
So, since you're looking for select boxes (either single-select or multi-select), first, I'd recommend creating view models for your entities. For example, with Tip:
public class TipViewModel
{
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required]
public int? SelectedPartnerId { get; set; }
public IEnumerable<SelectListItem> PartnerChoices { get; set;}
[Required]
public int? SelectedBookId { get; set; }
public IEnumerable<SelectListItem> BookChoices { get; set; }
}
Here, I've added nullable int (using a nullable allows them to be initially unselected, instead of just set to the first option) properties to track the id of the selected Book/Partner because it doesn't appear you have explicit properties on your entities for the foreign keys. That's fine, but it doesn't make it slightly more complicated to save the relationship, as you'll see in a bit. If you did have explicit foreign key properties, then you should mirror those in your view models instead.
Now in the GET version of your action, you'll need to do something like the following:
public ActionResult Create()
{
var model = new TipViewModel();
PopulateChoices(model);
return View(model);
}
...
protected void PopulateChoices(TipViewModel model)
{
model.PartnerChoices = db.Partners.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.Name
});
model.BookChoices = db.Books.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = string.Format("{0} by {1}", m.Name, m.Author)
});
}
I've abstracted out the code for populating these select lists because the code will be used multiple times throughout your controller. Also, I used string.Format on the Text value for the books just to show that you can do whatever you want with the text for the select list item. Also, the code above would be for a create action, obviously. Doing an edit would be similar but slightly different:
public ActionResult Edit(int id)
{
var tip = db.Tips.Find(id);
if (tip == null)
{
return new HttpNotFoundResult();
}
var model = new TipViewModel
{
Name = tip.Name,
Description = tip.Description,
SelectedPartnerId = tip.Partner != null ? tip.Partner.Id : new int?(),
SelectedBookId = tip.Book != null ? tip.Book.Id : new int?()
}
PopulateChoices(model);
return View(model);
}
The main difference is that you're obviously dealing with an existing instance so you need to pull it from the database. Then, you just need to map the data from your entity onto your view model. Since, again, you don't have explicit foreign key properties, you have to do a little extra leg work to get the currently chosen Partner/Book values, otherwise you could just copy the values for the foreign key properties over directly. Also, here, I'm just doing a manual mapping, but there's third-party libraries to make this task easier (see: AutoMapper).
With that, you can implement your views. Everything will work the same as it did when you were using the entity directly, you just need to make a couple of modifications. First, you'll need to change your view's model declaration:
#model Namespace.To.TipViewModel
Then, add the select lists for your two related properties:
#Html.DropDownListFor(m => m.SelectedPartnerId, Model.PartnerChoices)
...
#Html.DropDownListFor(m => m.SelectedBookId, Model.BookChoices)
The fun happens in the POST version of your actions. Most of the code will stay the same from the GET version, but now you'll have an if (ModelState.IsValid) block:
[HttpPost]
public ActionResult Create(TipViewModel model)
{
if (ModelState.IsValid)
{
// map the data from model to your entity
var tip = new Tip
{
Name = model.Name,
Description = model.Description,
Partner = db.Partners.Find(model.SelectedPartnerId),
Book = db.Books.Find(model.SelectedBookId)
}
db.Tips.Add(tip);
db.SaveChanges();
return RedirectToAction("Index");
}
// Form has errors, repopulate choices and redisplay form
PopulateChoices(model);
return View(model);
}
The edit version, again, is similar, except you're going to map onto you existing instance, for example:
tip.Name = model.Name;
tip.Description = model.Description;
tip.Partner = db.Partners.Find(model.SelectedPartnerId);
tip.Book = db.Books.Find(model.SelectedBookId);
That's all there is to it for reference properties. You don't actually have any thing that's M2M or even one-to-many on your entities in your question. Everything is one-to-one, but if you did have a collection property, you'd need to handle it slightly differently. You still need a property on your view model to hold the selected values and the available choices:
public List<int> SelectedFooIds { get; set; }
public IEnumerable<SelectListItem> FooChoices { get; set; }
Populating the choices would also be the same. The options are the options; it doesn't matter if you're select just one or many as far as that is concerned.
Mapping onto your entity in your create action would be different though, as you'd need to select all of the chosen items from the database and set your collection property on your entity to that:
var tip = new Tip
{
...
Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id)),
}
And, you'd need to make changes to both the GET and POST versions of your edit action. For the GET, you need to condense your collection property down to a list of ids:
var model = new TipViewModel
{
...
SelectedFooIds = tip.Foos.Select(m => m.Id).ToList(),
}
And in the edit version, you set new selected items:
tip.Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id);
Finally, in your views, you'd use ListBoxFor instead of DropDownListFor to enable the multiselect:
#Html.ListBoxFor(m => m.SelectedFooIds, Model.FooChoices)
So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.
I've been looking for a proper way to mark a property to NOT be changed when updating a model in MVC.
For example, let's take this small model:
class Model
{
[Key]
public Guid Id {get; set;}
public Guid Token {get; set;}
//... lots of properties here ...
}
then the edit method MVC creates looks like this:
[HttpPost]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
now if my View does not contain the Token, it will be nullified through that edit.
I'm looking for something like this:
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();
The best way so far I found is to be inclusive and set all properties I want included by hand, but I really only want to say which ones to be excluded.
we can use like this
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
it will update but without Token property
Anyone looking for how to achieve this on EF Core. It's basically the same but your IsModified needs to be after you add the model to be updated.
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
#svendk updated:
And if you (as me) are wondering why model don't have the token either before or after db.SaveChanges(), it's because with Update, the entity is actually not retrieved - only an SQL Update clause is sent - so the context don't know of your model's preexisting data, only the information you gave it in db.Update(mode). Even if you Find(model.id) you are not getting your context updated, as there is already loaded a model in the context, it is still not retrieved from database.
If you (as me) wanted to return the finished model as it looks like in the database, you can do something like this:
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
// New: Reload AFTER savechanges, otherwise you'll forgot the updated values
db.Entry(model).Reload();
Now model is loaded from database with all the values, the updated and the (other) preexisting ones.
Create new model that will have limited set of properties that you want to update.
I.e. if your entity model is:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public bool Enabled {get;set;}
}
You can create custom view model that will allow user to change Name, but not Enabled flag:
public class UserProfileModel
{
public int Id {get;set;}
public string Name {get;set;}
}
When you want to do database update, you do the following:
YourUpdateMethod(UserProfileModel model)
{
using(YourContext ctx = new YourContext())
{
User user = new User { Id = model.Id } ; /// stub model, only has Id
ctx.Users.Attach(user); /// track your stub model
ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
ctx.SaveChanges();
}
}
When you call this method, you will update the Name, but Enabled property will remain unchanged. I used simple models, but I think you'll get the picture how to use it.
I made an easy way to edit properties of entities I will share with you.
this code will edit Name and Family properties of entity:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
And this code will ignore to edit Name and Family properties of entity and it will edit another properties:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
Use this extension:
public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
where TEntity : class
{
var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
if (find == null)
throw new Exception("id not found in database");
if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (!properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
context.SaveChanges();
}
public enum TypeOfEditEntityProperty
{
Ignore,
Take
}
I guess you don't want the property to be changed just in some cases, because if you are not going to use it never in your application, just remove it from your model.
In case you want to use it just in some scenarios and avoid its "nullification" in the case above, you can try to:
Hide the parameter in the view with HiddenFor:
#Html.HiddenFor(m => m.Token)
This will make your original value to be kept unmodified and passed back to the controller.
Use TryUpdateModel: http://msdn.microsoft.com/en-us/library/dd460189(v=vs.108).aspx
Load again your object in the controller from your DBSet and run this method. You can specify both a white list and a blacklist of parameters that shall or shall not be update.
I use dapper but my solution will work for EF too. If you are potentially going to change your ORM in the future my solution might be better for you.
class Model
{
public Foo { get; set; }
public Boo { get; set; }
public Bar { get; set; }
// More properties...
public void SafeUpdate(Model updateModel, bool updateBoo = false)
{
// Notice Foo is excluded
// An optional update
if (updateBoo)
Boo = updateModel.Boo;
// A property that is always allowed to be updated
Bar = updateModel.Bar;
// More property mappings...
}
}
As you can observe I allow updates for only the properties that I wish.
A downside of my approach is that you'll need to manually update this method if you introduce new properties (that are allowed to be updated) to your model. But I believe this in not always a downside but sometimes an advantage, in the sense that you'll need to be aware of what is being updated, this might be beneficial in terms of security.
Let us see a demonstration of this approach.
// Some code, DI etc...
public IActionResult Put([FromBody] Model updateModel)
{
var safeModel = new Model();
safeModel.Update(updateModel);
// Add validation logic for safeModel here...
_modelRepository.Update(safeModel);
}