EF 4.1 - Update Properties On Child Collection - asp.net-mvc-3

I have searched hi and low and I am stuck here.
I am using EF 4.1 in an MVC3 app, with the Service/Repository/UnitOfWork pattern and AutoMapper to map my models and entities.
So I have a really basic situation; I have a collection of ChildProducts that have a collection of PriceTiers.
My view models look like this:
AddEditChildProductModel
public class AddEditChildProductModel
{
#region "Fields/Properties"
public ActionType ActionType { get; set; }
public string FormAction { get; set; }
public int ID { get; set; }
public int ProductID { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public decimal MSRP { get; set; }
public decimal RetailPrice { get; set; }
public int Servings { get; set; }
public decimal Weight { get; set; }
public bool Display { get; set; }
public int DisplayIndex { get; set; }
public IEnumerable<AddEditPriceTierModel> PriceTiers { get; set; }
#endregion
#region "Constructor(s)"
#endregion
#region "Methods"
#endregion
}
AddEditPriceTierModel
public class AddEditPriceTierModel
{
#region "Fields/Properties"
public int ID { get; set; }
public int ChildProductID { get; set; }
public decimal Price { get; set; }
public int QuantityStart { get; set; }
public int QuantityEnd { get; set; }
public bool IsActive { get; set; }
#endregion
#region "Constructor(s)"
#endregion
#region "Methods"
#endregion
}
In the controller action, I am simply trying to map the changed PriceTier properties:
public ActionResult EditChildProduct(AddEditChildProductModel model)
{
if (!ModelState.IsValid)
return PartialView("AddEditChildProduct", model);
ChildProduct childProduct = productService.GetChildProductByID(model.ID);
AutoMapper.Mapper.Map<AddEditChildProductModel, ChildProduct>(model, childProduct);
UnitOfWork.Commit();
return ListChildProducts(model.ProductID);
}
And I am getting this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
When stepping into the action, the models/entities are mapped correctly, I don't get it!!

Eranga is right. I'm guessing your productService does not call AsNoTracking on the ef context before returning the ChildProduct. If not, this means what it returns is still attached to the context. When automapper does its thing, it replaces the whole collection, which can orphan the attached child entities that were not part of the form submission. Since the orphans don't have a non-null foreign key, they must be deleted from the context before calling SaveChanges. If they are not, you get this infamous exception.
On the other hand, if your productService calls AsNoTracking on the context before returning the entity, it will not track changes, and will not try to delete any orphaned items that do not exist in the collection created by automapper.

Related

How to update an existing object in a many to many relationship (.Net 5)

I have been using the .Net 5 and EF Core 5 for a small web app. Given EF Core 5 supports many - many out of the box there is no need for a joining table.
I've run into an issue when updating a object that already exists in the DB. For my app I have Athletes and Parents which have the many - many relationship.
public class Athlete
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Parent> Parents { get; set; }
}
public class Parent
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Athlete> Athletes { get; set; }
}
When I try to update the existing athlete that has a relation ship with two other parents I get an error:
Violation of PRIMARY KEY constraint 'PK_AthleteParent'. Cannot insert
duplicate key in object 'dbo.AthleteParent'. The duplicate key value
is (31, 1)
[HttpPost]
public async Task<ActionResult<Athlete>> PostAthlete(Athlete athlete)
{
_context.Athletes.Update(athlete);
await _context.SaveChangesAsync();
return Ok(athlete));
}
From what I can tell when entity tries to update my Athlete it tries to insert new rows into the joining table even though the parents already exist in there. Is there a way to get entity to remove any records when the relationship is updated? Or is there a way to tell entity to take update the joining table to match the Athlete object that is passed in?
Given a simple example like this:
public class Foo {
Guid Id { get; set; }
ICollection<Bar> Bars { get; set; }
}
public class Bar {
Guid Id { get; set; }
ICollection<Foo> Foos { get; set; }
}
You can call clear() on a tracked instance of Foo, and then re-add the Bar instances that you want assigned. I've found this is a nice way to avoid the constraint exception - much easier than manually trying to figure out what Bars have changed.
var foo = context.Foos.Include(x => x.Bars).FirstOrDefault(x => x.Id == someGuid);
foo.Bars.Clear();
foo.Bars.Add(bar1);
foo.Bars.Add(bar2);
...
context.Update(foo);
context.SaveChanges();

How do I write the Linq query to get complex model items?

I'm using EF Code First. Now, I'm having a hard time figuring out how to write the LINQ to retrieve the data into my models in my Controller, to display them in a view. Basically, I am receiving a feed of HolterTest data, and I am trying to create a worklist for the people who do a bunch of specific tasks to process the HolterTest, allowing them to flag the tasks as they are completed, and provide status of where the individual tests are in the process The basic Task class is so they can add or alter steps in the process, with the displayOrder being the order in which tasks are done. A WorkTask is a specific instance of a task, allowing us to mark who completed it, and when. A WorkItem is the complex type that includes the HolterTest, the list of WorkTasks, and status information, including when the WorkTasks were all completed.
Model Classes:
public class HolterTest
{
public Int32 HolterTestID { get; set; }
public string PatientNumber { get; set; }
public string LastName { get; set; }
public DateTime RecordingStartDateTime { get; set; }
public System.Nullable<DateTime> AppointmentDateTime { get; set; }
}
public class Task
{
public Int32 TaskID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Int32 DisplayOrder { get; set; }
public bool IsActive { get; set; }
}
public class WorkTask
{
public Int32 WorkTaskID { get; set; }
public Task Task { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
public string CompletedBy { get; set; }
}
public class WorkItem
{
public Int32 WorkItemID { get; set; }
public HolterTest HolterTest { get; set; }
public string Status { get; set; }
public List<WorkTask> WorkTasks { get; set; }
public bool IsStarted { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
}
Currently I have a business logic function that takes the list of HolterTests, finds the ones that don't have a WorkItem, and creates the WorkItems, associates the HolterTests including the WorkTasks, based on the current active list of Tasks.
My problem is how to write the LINQ to get all of the WorkItems (with their child items) for my WorkItemController so I can display the work to do in a View (WorkItems where IsCompleted = false) by PatientNumber, and make it possible to update WorkTasks for a particular WorkItem.
You want to access the related using it's navigation properties. Note that in your example, you haven't setup the navigation properties to be virtual. You should should update your model like this:
public class WorkItem
{
public Int32 WorkItemID { get; set; }
public virtual HolterTest HolterTest { get; set; }
public string Status { get; set; }
public virtual List<WorkTask> WorkTasks { get; set; }
public bool IsStarted { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
}
A simple function for accessing the work items by patient number is this:
IEnumerable<WorkItem> GetWorkIncompleteWorkItemsByPatient(string patientNumber)
{
var db = new YourContext();
return db.WorkItems.Where(wi => wi.IsCompleted == false && wi.HolterTest.PatientNumber == patientNumber);
}
Then to work on the related tasks, you would access it through the task, in this example if you knew the task ID:
var workTask = YourWorkItem.WorkTasks.FirstOrDefault(wt => wt.WorkTaskID == worktaskId);
You could look through all the tasks in the work item like this:
foreach (var workTask in YourWorkItem.WorkTasks)
{
//your logic here...
}
Linq to entities explained
http://msdn.microsoft.com/en-us/library/bb399367.aspx
But starting here may suit better: The EF Main Site http://msdn.microsoft.com/en-us/data/ee712907
if you really want to dive straight intry this video and sample code. http://msdn.microsoft.com/en-us/data/jj193542
then see this http://msdn.microsoft.com/en-us/data/jj573936
Essentially based on the POCO you have you could read per POCO and get the data that way.
However EF does a lot of heavy lifting if the POCOS have navigational properties and foreign keys defined. Worth revisiting the POCO definitions and Code-First patterns.

What is the proper sequence of method calls when using a multi layered architecture?

I have built a simple survey-tool using MVC 3 with only 1 layer (MVC). I regret this now. All my database access and mapping is handled in the controllers, and some other mapping classes.
I would like to switch over to using three layers:
Presentation (MVC)
Business Logic
Data / Persistence (EF)
I am using the Entity Framework to handle everything with the database. The entity framework creates it's own domain classes. Where should the mapping between the Models that MVC uses and the models that EF creates go?
If the mapping is in the business layer, is there a need for the Models folder in the MVC project?
A survey-question consists of the Question itself, Rows and Columns. Theese are the models that i use:
public class Question {
public int Question_ID { get; set; }
public Boolean Condition_Fullfilled;
[Required(ErrorMessage = "Dette felt er påkrævet")]
public String Question_Wording { get; set; }
public String Question_Type { get; set; }
[Required(ErrorMessage = "Dette felt er påkrævet")]
public String Question_Order { get; set; }
public String Left_scale { get; set; }
public String Right_scale { get; set; }
public int Scale_Length { get; set; }
public String Left_Scale_HelpText { get; set; }
public String Right_Scale_HelpText { get; set; }
public Boolean Visible { get; set; }
public Boolean IsAnswered { get; set; }
public String Question_HelpText { get; set; }
public int Category_ID { get; set; }
}
public class MatrixColumns
{
public int Column_ID { get; set; }
public int Column_Number { get; set; }
public String Column_Description { get; set; }
public Boolean IsAnswer { get; set; }
public int? Procent { get; set; }
public bool Delete { get; set; }
public bool Visible { get; set; }
public int? Numbers { get; set; }
public String Help_Text { get; set; }
}
public class MatrixRows
{
public bool Delete { get; set; }
public bool Visible { get; set; }
public int Row_Id { get; set; }
public String Row_Number { get; set; }
public String Row_Description { get; set; }
public String Special_Row_CSS { get; set; }
public String Help_Text { get; set; }
// Dette er summen af procenterne af alle kolonner i rækken
public int RowSum { get; set; }
}
All the data for theese models is retrieved in the Controller, based upon a QuestionID, and mapped to a ViewModel that looks like this:
public class ShowMatrixQuestionViewModel : Question
{
public Dictionary<MatrixRows, List<MatrixColumns>> columnrow { get; set; }
public List<MatrixColumns> columns { get; set; }
public List<MatrixRows> rows { get; set; }
public ShowMatrixQuestionViewModel()
{
columns = new List<MatrixColumns>();
rows = new List<MatrixRows>();
columnrow = new Dictionary<MatrixRows, List<MatrixColumns>>();
}
}
So when i want to send a ShowMatrixQuestionViewModel to a View from my Controller, what is the route i should take?
This is my suggestion:
-> Controller calls a method in the business layer called
public ShowMatrixViewModel GetQuestion(int QuestionID) {}
-> GetQuestion calls the following methods in the data layer:
public Question GetQuestion(int QuestionId) {}
public MatrixRows GetRows(int QuestionId) {}
public MatrixColumns GetColumns(int id) {}
-> Entity framework returns "pure" objects, which i want to map over to the ones i posted above
-> GetQuestion calls methods to map the EF models to my own models
-> Last GetQuestion calls a method that maps the Questions, Rows and Columns:
ShowMatrixQuestionViewModel model = MapShowMatrixQuestionViewModel(Question, MatrixRows, MatrixColumns)
return model;
Is this correct?
Thanks in advance
To answer the first part of your question:
"Where should the mapping between the Models that MVC uses and the models that EF creates go?"
The answer is that the models MVC uses are the models created by the EF. Your EF tool in the ASP.NET MVC project is either Linq to SQL Classes or the ADO.NET Entity Framework Model. You should create these inside the Models folder in your project and they provide your data / persistence (EF).

Can't form some simple POCO's to use with "Code First" Entity Framework, please check for mistake

So I decided to go with the code first/DbContext approach, but already have an existing database file. Nothing complex, so I am thinking I can just create the DbContext derived container class with DbSets for the respective POCO's, create the connection string to my database and I should be set. However I believe I am having difficulties properly declaring the properties in my entity classes since I am getting errors when trying access an object through the navigational properties. Usually telling me Object reference not set to an instance of an object when I try context.Products.Find(1).Category.CATNAME; etc. Also tried declaring the collection properties with virtual keyword to no avail.
Some specifics of the database schema are:
In Categories table the PCATID is a foreign key to the CategoryID in
the same Categories table and can be null.
Both CategoryID and RootCategoryID in Products table can be null and
are both foreign keys to CategoryID in the Categories table.
I am testing things at the moment but will be setting a lot of the fields to non null types eventually.
Here are my entity POCO's and the entity Dbset container class:
public class Category
{
[Key]
public int CategoryID { get; set; }
public string CATNAME { get; set; }
public int PCATID { get; set; }
public ICollection<Category> Categories { get; set; }
public ICollection<Product> Products { get; set; }
}
public class Product
{
[Key]
public int ProductID { get; set; }
public int CategoryID { get; set; }
public int RootCategoryID { get; set; }
public string Name { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public string Keywords { get; set; }
public decimal ListPrice { get; set; }
public Category Category { get; set; }
}
public class EFDbContext: DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
You need to make PCATID a nullable property as you have said it can be null. Make all those navigation properties and collection properties virtual. EF will not be able to detect the category hierarchy so you have use either attributes or fluent API to configure that.
public class Category
{
[Key]
public int CategoryID { get; set; }
public string CATNAME { get; set; }
[ForeignKey("ParentCategory")]
public int? PCATID { get; set; }
[InverseProperty("Categories")]
public virtual Category ParentCategory { get; set; }
[InverseProperty("ParentCategory")]
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Requirements for Creating POCO Proxies
Everything looks ready for POCO but Lazy Loading isn't sorted out at this point. By default LL is on, but in order to enable lazy loading, the Category property must be Virtual (a proxy is created that catches the reference and loads the data). If you don't want lazy loading then disable it in your EFDbContext constructor.
So your options are:
public virtual Category Category { get; set; }
or
public class EFDbContext: DbContext
{
public static EFDbContext()
{
LazyLoadingEnabled = false
}
...
}
You'd probably want to do the first one...
Are you certain you really want to use Code First? Or do you just want to use DbContext and DbSet? You can get the same benefits with Database First, using DbContext and DbSet. Since you already have a database, it's generally a lot simpler.
See: http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-model-amp-database-first-walkthrough.aspx
The only difference between Code First and Database First with DbContext is that Code first uses the fluent mapping model, while Database First uses an .edmx file. Maintaining the .edmx is much easier with an existing database.
If you're bound and determined to use Code First, then I suggest getting the Entity Framework Power Tools CTP1 and reverse engineering your database to Code First.
I agree with #Eranga about class Category (+1 to #Eranga).
public class Category {
[Key]
public int CategoryID { get; set; }
public string CATNAME { get; set; }
[ForeignKey("ParentCategory")]
public int? PCATID { get; set; }
[InverseProperty("Categories")]
public virtual Category ParentCategory { get; set; }
[InverseProperty("ParentCategory")]
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
And you also have problem with your Linq query:
context.Products.Find(1).Category.CATNAME;
EF return data only from tables that you request with Include or you use this tables in functions.
With this code all work:
db.Products
.Include(p => p.Category) // here I demand to load data from Category table
.First(p => p.ProductID == 3)
.Category
.CATNAME;

asp.net mvc 3 entity framework, passing model info in Get request of create action

I'm having trouble passing view information from my Get/Create action to my view. Here are my three model classes;
public class Competition
{
public int Id { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Prize { get; set; }
}
public class CompetitionEntry
{
public int Id { get; set; }
public int CompetitionEntryId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompetitionId { get; set; }
}
public class CompetitionEntryViewModel
{
public int Id { get; set; }
public Competition Competitions { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Here is my Get/Create action in CompetitionEntry Controller;
public ActionResult Create(int id)
{
CompetitionEntryViewModel competitionentryviewmodel = db.CompetitionEntriesView.Find(id);
return View(competitionentryviewmodel);
}
I know this doesn't work. The id parameter goes into the URL fine. How to I get access to my Competition class in th Get action? I need to be able to show the competion name on my Create Competition entry view.
Thanks in advance!
public ActionResult Create(int id)
{
var data = db.CompetitionEntriesView.Find(id);
CompetitionEntryViewModel competitionentryviewmodel = new CompetitionEntryViewModel();
competitionentryviewmodel.CompetitionName = data.Name;
return View(competitionentryviewmodel);
}
What you are trying to do is build an object graph and display it through a view model. In order to do this, you need to map your domain model(s) to your view model.
You can do the mapping yourself by writing a lot of code (re-inventing the wheel), or, you could consider using third party tools to do this for you. I recommend you use an AutoMapper as it is very simple to use imo.
The other problem is that your view model contains a domain model. This is likely to cause you a lot of headache in near future. If I were you, I would replace Competition with CompetitionViewModel.
I would also consider creating a view model for a list of competitions, i.e. CompetitionsViewModel. Look into partial views to see how you can display a list of competitions.
Good luck

Resources