Trouble Saving object and Child Objects to the database? - c#

I am having trouble to see what i need to correct or add to my controller in order to have my data saved to the database. See below what i have so far.
Create method
[HttpPost]
public ActionResult Create(Team model)
{
if (ModelState.IsValid)
{
new Team
{
Name = model.Name,
Division = model.Division,
Description = model.Description,
TeamContact = new TeamContact
{
EmailAddress = model.TeamContact.EmailAddress,
PhoneNumber = model.TeamContact.PhoneNumber,
TeamAddress = new TeamAddress
{
Box = model.TeamContact.TeamAddress.Box,
StreetName = model.TeamContact.TeamAddress.StreetName,
StreetNumber = model.TeamContact.TeamAddress.StreetNumber,
City = model.TeamContact.TeamAddress.City,
PostalCode = model.TeamContact.TeamAddress.PostalCode,
Province = model.TeamContact.TeamAddress.Province
}
}
};
_dataSource.Save();
}
return View(model);
Table Relationships
-Team one-to-one TeamContact
-TeamContact one-to-on TeamAddress
IDataSource Interface
IQueryable<Team> Teams { get; }
IQueryable<TeamAddress> TeamAddresses { get; }
IQueryable<TeamContact> TeamContacts { get; }
void Save();
Context class
public DbSet<Team> Teams { get; set; }
IQueryable<Team> IDataSource.Teams
{
get { return Teams; }
}
public DbSet<TeamAddress> TeamAddresses { get; set; }
IQueryable<TeamAddress> IDataSource.TeamAddresses
{
get { return TeamAddresses; }
}
public DbSet<TeamContact> TeamContacts { get; set; }
IQueryable<TeamContact> IDataSource.TeamContacts
{
get { return TeamContacts; }
}
public void Save()
{
SaveChanges();
}
What am i missing to have my data saved to the database?

You don't have any code in your controller that is actually adding the Team to your database. Right now you are simply creating a new Team object in memory and saving changes to your database. Nothing is actually being added to the database to save.
Team team = new Team
{
.. the rest of your model building code here ..
};
_dataSource.Teams.Add(team); // This adds the Team entity to the database
_dataSource.Save();

Related

Entity Framework Complex Type Validation (DB First)

I'm currently using Entity framework 6 to interface between the user interface and the back end database, however I have a problem with validation on fields in a complex type. I'm using the database first approach.
I created my model based on existing database, then converted fields into a complex type.
For example, user model.
public partial class User {
public User() {
this.DeliveryAddress = new Address();
this.InvoiceAddress = new Address();
}
public Address DeliveryAddress { get; set; }
public Address InvoiceAddress { get; set; }
}
Complex type
public partial class Address {
public string Address1 { get; set; }
public string Address2 { get; set; }
public string CompanyName { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string Firstname { get; set; }
public string Postcode { get; set; }
public string Surname { get; set; }
public string TownCity { get; set; }
}
I mapped the table correctly in the model browser and everything is compiling. However when trying to save it throws an error that validation failed. After a bit of investigation it turns out for some reason all the complex type fields are required. Which I find strange as they are strings which can be null and even in database the fields are nullable. As a work around I created a partial class of Address with a constructor to initialize all the fields to an empty string which is a temporary work around. However this isn't ideal.
Code which saves the details. (Mvc Action)
public ActionResult UpdateDetails(UserDetailsViewModel info) {
try {
if (ModelState.IsValid) {
var user = db.Users.Find(info.UserID);
if (user != null) {
user.DeliveryAddress = new Data.Address {
CompanyName = info.DeliveryCompanyName,
Address1 = info.DeliveryAddress1,
Address2 = info.DeliveryAddress2,
Firstname = info.DeliveryFirstname,
Postcode = info.DeliveryPostcode,
Surname = info.DeliverySurname,
TownCity = info.DeliveryCity,
};
user.InvoiceAddress = new Data.Address();
if (!info.IsInvoiceAddress) {
user.InvoiceAddress = new Data.Address {
Address1 = info.InvoiceAddress1,
Address2 = info.InvoiceAddress2,
Firstname = info.InvoiceFirstname,
Postcode = info.InvoicePostcode,
Surname = info.InvoiceSurname,
TownCity = info.InvoiceCity,
CompanyName = info.InvoiceCompanyName
};
}
db.SaveChanges();
}
return Json(new { Msg = "Ok" });
}
} catch (System.Data.Entity.Validation.DbEntityValidationException dbEx) {
foreach (var valErs in dbEx.EntityValidationErrors) {
foreach (var valEr in valErs.ValidationErrors) {
System.Diagnostics.Trace.TraceInformation("Property: {0} Error: {1}", valEr.PropertyName, valEr.ErrorMessage);
}
}
return Json(new { Msg = "Err" });
} catch {
return Json(new { Msg = "Err" });
}
return PartialView("UserDetailsForm", info);
}
Has anyone come across a similar situation? Maybe Im missing something which needs to be configured.
After much searching, I found a solution to my issue. In the model browser options under complex types, you can expand the complex types to reveal the fields in the complex type and in the properties of each one there is a property 'Nullable' which is set to false. I set these to true, and it now saves without issues. Im quite surprised it doesnt pick up the type and change it to allow nulls depending on the type.

How to check in neat way if collection contains an object which's one property is equal to the object passed to Contains method?

I have a model class:
public class AuthorizationToView {
public int Id { get; set; }
public virtual Person CustomerToVisit { get; set; }
public virtual ApplicationUser User { get; set; }
public DateTime TimeStamp { get; set; }
}
The ApplicationDbContext db has a property: public DbSet<AuthorizationToView> AuthorizationsToView { get; set; }
And the ApplicationUser has a property public virtual ICollection<AuthorizationToView> PersonsPermittedToView { get; set; }
ISSUE:
Now in one place in code I try to check if user have inside hist public virtual ICollection<AuthorizationToView> PersonsPermittedToView { get; set; } an AuthorizationToView which has certain Person as property and if not return BadRequest.
I try this:
if (User.IsInRole("RegularUser")) {
var user = UserManager.FindById(User.Identity.GetUserId());
if (!user.PersonsPermittedToView.Contains(new AuthorizationToView { CustomerToVisit = person })) {
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
but unfortunately !user.PersonsPermittedToView.Contains(new AuthorizationToView { CustomerToVisit = person }) is always true.
I could override Equals method but I am affraid that it will confuse Entity Framework.
Assuming the Person has a primary key property called Id, this is how I would achieve the same thing:
if (User.IsInRole("RegularUser"))
{
var user = UserManager.FindById(User.Identity.GetUserId());
if (!user.PersonsPermittedToView.Any(pptv =>
pptv.CustomerToVisit.PersonToVisit.Id == person.Id))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
You could just compare the person directly, rather than creating the new AuthorizationToView, like this:
if (!user.PersonsPermittedToView.Select(a=>a.CustomerToVisit).Contains(person))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Or if you actually should be checking on Id, use:
if (!user.PersonsPermittedToView.Select(a=>a.CustomerToVisit.Id).Contains(person.Id))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}

EF adding duplicate records into lookup/reference table

I have 3 tables,
1. AttributeTypes (Columns: AttributeId (PK), AttributeName, ..)
2. Location (Columns: locationId (PK), LocationName, ...)
3. LocationAttributeType (Columns: locationId (FK), AttributeId (FK))
Whenever I am trying to insert new location record along with its attribute type from GUI, it should create new record for Table- Location and LocationAttributeType. But EF trying to add new record in Table- AttributeTypes as well, which is just used as reference table and should not add new/duplicate records in it. How can I prevent that?
here is my code,
The model which GUI sends is,
public class LocationDataModel
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Code { get; set; }
[DataMember]
public List<AttributeTypeDataModel> AssignedAttributes = new List<AttributeTypeDataModel>();
}
public class AttributeTypeDataModel
{
protected AttributeTypeDataModel() {}
public AttributeTypeDataModel(int id) { this.Id = id; }
public AttributeTypeDataModel(int id, string name)
: this(id)
{
this.Name = name;
}
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<LocationDataModel> Locations { get; set; }
}
The Entities created by EF are,
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<AttributeType>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<AttributeType> AttributeTypes { get; set; }
}
public partial class AttributeType
{
public AttributeType()
{
this.Locations = new List<Location>();
}
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
I have below code to Add these new location to database,
private IEnumerable<TEntity> AddEntities<TModel, TEntity, TIdentityType>
(IEnumerable<TModel> models, Func<TModel, TIdentityType> primaryKey,
IGenericRepository<TEntity, TIdentityType> repository)
{
var results = new List<TEntity>();
foreach (var model in models)
{
var merged = _mapper.Map<TModel, TEntity>(model);
var entity = repository.Upsert(merged);
results.Add(entity);
}
repository.Save();
return results.AsEnumerable();
}
I am using following generic repository to do entity related operations
public TEntity Upsert(TEntity entity)
{
if (Equals(PrimaryKey.Invoke(entity), default(TId)))
{
// New entity
return Context.Set<TEntity>().Add(entity);
}
else
{
// Existing entity
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
public void Save()
{
Context.SaveChanges();
}
Whats wrong I am doing here?
The DbSet<T>.Add() method attaches an entire object graph as added. You need to indicate to EF that the 'reference' entity is actually already present. There are two easy ways to do this:
Don't set the navigation property to an object. Instead, just set the corresponding foreign key property to the right value.
You need to ensure that you don't load multiple instances of the same entity into your object context. After creating the context, load the full list of AttributeType entities into the context and create a Dictionary<> to store them. When you want to add an attribute to a Location retrieve the appropriate attribute from the dictionary. Before calling SaveChanges() iterate through the dictionary and mark each AttributeType as unchanged. Something like this:
using (MyContext c = new MyContext())
{
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Fish", AttributeTypeId = 1 });
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Face", AttributeTypeId = 2 });
c.SaveChanges();
}
using (MyContext c = new MyContext())
{
Dictionary<int, AttributeType> dictionary = new Dictionary<int, AttributeType>();
foreach (var t in c.AttributeTypes)
{
dictionary[t.AttributeTypeId] = t;
}
Location l1 = new Location(1, "Location1") { AttributeTypes = { dictionary[1], dictionary[2] } };
Location l2 = new Location(2, "Location2") { AttributeTypes = { dictionary[1] } };
// Because the LocationType is already attached to the context, it doesn't get re-added.
c.Locations.Add(l1);
c.Locations.Add(l2);
c.SaveChanges();
}
In this specific case you are using a many-to-many relationship, with EF automatically handling the intermediate table. This means that you don't actually have the FK properties exposed in the model, and my first suggestion above won't work.
Therefore, you either need to use the second suggestion, which still ought to work, or you need to forgo the automatic handling of the intermediate table and instead create an entity for it. This would allow you to apply the first suggestion. You would have the following model:
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<LocationAttribute>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<LocationAttribute> AttributeTypes { get; set; }
}
public partial class LocationAttribute
{
[ForeignKey("LocationId")]
public Location Location { get; set; }
public int LocationId { get; set; }
public int AttributeTypeId { get; set; }
}
public partial class AttributeType
{
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
}
With this approach you do lose functionality since you can't navigate from a Location to an AttributeType without making an intermediate lookup. If you really want to do that, you need to control the entity state explicitly instead. (Doing that is not so straightforward when you want to use a generic repository, which is why I've focused on this approach instead.)
Thank you all for your suggestions.
I have to get rid of my generic repository here to save my context changes and do it manually as below,
private IEnumerable<int> AddLocationEntities(IEnumerable<LocationDataModel> locations)
{
var results = new List<int>();
foreach (LocationDataModel l in locations)
{
var entity = _mapper.Map<LocationDataModel, Location>(l);//you can map manually also
var AttributeCode = l.AssignedAttributes.FirstOrDefault().AttributeTypeId;
using (MyContext c = new MyContext())
{
var attr = c.AttributeTypes.Where(a => a.Id == AttributeTypeId ).ToList();
entity.AttributeTypes = attr;
c.Locations.Add(entity);
c.SaveChanges();
var locid = entity.Id;
results.Add(locid);
}
}
return results;
}
In the else statement of yourUpsert you should add
context.TEntity.Attach(entity);

Update multiple tables in MVC Edit Action using repository

I have a pair of ViewModels that references data from a number of tables. One for displaying and one for editing.
When I return data from the display ViewModel I can map all the relevant fields using ValueInjecter InjectFrom functionality.
What do I do next to get the database to update?
If I send the models to my Update method in the repository I can see the changes in the model but the context doesn't pick them up. Am I missing a step or is there a better way of doing this?
If I try to modify one table at a time I can get the context to pick up the changes but then get an error as follows:
Store update, insert, or delete statement affected an unexpected
number of rows (0).
---EDIT---
I've updated the code and moved the mapping into the repository but I'm still getting the same error even though the debugger shows the entities with the new values.
ViewModels
public partial class HouseholdEditViewModel //for displaying in browser
{
public int entityID { get; set; }
public int familyID { get; set; }
public string UPRN { get; set; }
public string address { get; set; }
public HousingTypeDropDownViewModel housingTypeID { get; set; }
public KeyworkerDropDownViewModel keyworkerID { get; set; }
public string startDate { get; set; }
public bool loneParent { get; set; }
public string familyPhoneCode { get; set; }
public string familyPhone { get; set; }
}
public partial class HouseholdAddViewModel //for mapping to database
{
public int familyID { get; set; }
public string UPRN { get; set; }
public string address { get; set; }
public int entityTypeID { get; set; }
public int housingTypeID { get; set; }
public int keyworkerID { get; set; }
public DateTime startDate { get; set; }
public bool loneParent { get; set; }
public string familyPhoneCode { get; set; }
public string familyPhone { get; set; }
}
Repository (Current version - I've attempted a few different things without success)
public interface IHouseholdRepository : IDisposable
{
//other methods here...
void Update(HouseholdAddViewModel model, int id);
}
public void Update(HouseholdAddViewModel model, int id)
{
//check address exists
var address = (from u in context.tAddress
where model.UPRN.Contains(u.UPRN)
select u.UPRN);
var ae = new tAddressEntity();
ae.InjectFrom(model);
ae.entityID = id;
ae.UPRN = model.UPRN;
context.tAddressEntity.Attach(ae);
context.Entry(ae).State = EntityState.Modified;
var e = new tEntity();
e.InjectFrom(model);
e.entityID = id;
e.entityName = model.address;
e.tAddressEntity.Add(ae);
context.tEntity.Attach(e);
context.Entry(e).State = EntityState.Modified;
var a = new tAddress();
a.InjectFrom(model);
context.tAddress.Attach(a);
context.Entry(a).State = address.ToString() == string.Empty ?
EntityState.Added :
EntityState.Modified;
var hs = new tHousingStatus();
hs.InjectFrom(model);
hs.entityID = id;
context.tHousingStatus.Attach(hs);
context.Entry(hs).State = EntityState.Modified;
var k = new tKeyWorker();
k.InjectFrom(model);
k.entityID = id;
context.tKeyWorker.Attach(k);
context.Entry(k).State = EntityState.Modified;
var l = new tLoneParent();
l.InjectFrom(model);
l.entityID = id;
context.tLoneParent.Attach(l);
context.Entry(l).State = EntityState.Modified;
var h = new tHousehold();
h.InjectFrom(model);
h.entityID = id;
h.tHousingStatus.Add(hs);
h.tKeyWorker.Add(k);
h.tLoneParent.Add(l);
context.Entry(h).State = EntityState.Modified;
context.SaveChanges();
}
Controller
[HttpPost]
public ActionResult Edit(HouseholdAddViewModel model, int id)
{
model.entityTypeID = _repo.GetEntityType();
if (ModelState.IsValid)
{
_repo.Update(model, id);
return RedirectToAction("Index");
}
return View("Edit", id);
}
The easiest way to update an entity using EF is to retrieve the entity (using
it's key) and then apply the updates to that object instance. EF will automatically detect the updates to the entity and apply them when you call SaveChanges().
It seems as if you're creating new entities and you're not adding them to context so they
aren't being picked up.
I would change your Edit controller to do this
[HttpPost]
public ActionResult Edit(HouseholdAddViewModel model, int id)
{
model.entityTypeID = _repo.GetEntityType();
if (ModelState.IsValid)
{
var h = _repo.GetHousehold(id);
h.InjectFrom(model);
h.entityID = id;
//...
}
}

How to integrate a Linq to Entity query that queries multiple entities in a repository and send data to View?

I'm learning MVC, the repository pattern and EF and I need some advice as to how best to integrate a method which contains a query that queries multiple entities into the repository.
At the moment, I have created a repository class which implements an interface and uses an instance of DbContext to retrieve data from the database using the Entity Framework, but for one entity.
Edited...
I have the GetJourneys() method in my repository, however I am unsure how to obtain the Journey details from the query in the Controller. I can get the User details.
public IEnumerable<User> GetJourneys()
{
var todayDate = DateTime.Now;
//Woking with many to many Relationship
//(join tables) in Asp.net MVC/Entity Framework
var viewModel = from j in dbContext.Users
from u in dbContext.Journeys
where u.DepartDate >= todayDate.Date
orderby u.DepartDate ascending
select j;
return viewModel.ToList();
}
Below is my User entity
public class User
{
[Key, Required]
public int UserID { get; set; }
[MaxLength(30), Required]
public string FirstName { get; set; }
[MaxLength(30), Required]
public string LastName { get; set; }
[Required]
public string ProfileImg { get; set; }
[MaxLength(30), Required]
public string FbLink { get; set; }
public ICollection<Journey> Journeys { get; set; }
}
Below is my Controller
public ViewResult Search()
{
var userJourneyList = from user in repository.GetJourneys() select user;
var searchView = new List<SearchViewModel>();
try
{
if (userJourneyList.Any())
{
foreach (var user in userJourneyList)
{
searchView.Add(new SearchViewModel()
{
//JourneyDestination = user.Journeys.FromDestination,
//JourneyDate = user.Journeys.DepartDate,
UserName = user.FirstName,
ProfileImage = user.ProfileImg,
//SeatsAvailable = user.Journeys.SeatsAvailable,
//UserType = user.Journeys.UserType
});
}
returnAmount = 1;
ViewBag.Amount = returnAmount;
}
else
{
returnAmount = 0;
ViewBag.Amount = returnAmount;
}
var todayDate = DateTime.Now;
}
catch (NullReferenceException ex)
{
MessageBox.Show(ex.Message);
}
return View(searchView.ToList());
}
UPDATE In my repository now
public IList<User> GetAllUsersWithJourneys()
{
using (var db = new EfDbContext())
{
var users = from userJourney in db.Users.Include(i => i.Journeys)
select userJourney;
return users.ToList();
}
}
However, I still don't understand how to get the journey details. My User and Journey entities are correct in terms of a many-to-many relationship. Below is the controller with the new repository method.
var userJourney = repository.GetAllUsersWithJourneys();
var searchView = new List<SearchViewModel>();
try
{
if (userJourneyList.Any())
{
foreach (var user in userJourney)
{
searchView.Add(new SearchViewModel()
{
UserName = user.FirstName,
JourneyDestination = user.Journeys.ToDestination //ERROR
});
}
}
else
{
//user will be notified that no results were found and that they are given the option to create the journey that they seek
returnAmount = 0;
ViewBag.Amount = returnAmount;
}
}
catch (NullReferenceException ex)
{
MessageBox.Show(ex.Message);
}
return View(searchView.ToList());
My ViewModel looks like this
public class SearchViewModel
{
public string ProfileImage { get; set; } //User
public string JourneyDestination { get; set; } //Journey
public DateTime JourneyDate { get; set; } //Journey
public string UserName { get; set; } //User
public int SeatsAvailable { get; set; } //Journey
public bool UserType { get; set; } //Journey
}
If what you're trying to do is flatten all user journeys into a single list (assumption based on the shape of the model you're passing to the view), then one way to do it would be like this:
var userJourney = repository.GetAllUsersWithJourneys();
var searchView = new List<SearchViewModel>();
try
{
if (userJourneyList.Any())
{
foreach (var user in userJourney)
{
foreach(var journey in user.Journeys)
{
searchView.Add(new SearchViewModel()
{
UserName = user.FirstName,
JourneyDestination = journey.JourneyDestination
});
}
}
}
}
catch (NullReferenceException ex)
{
// ...
}
Alternatively, you could refactor it to be more functional:
var userJourney = repository.GetAllUsersWithJourneys();
var searchView = userJourney.SelectMany(
user => user.Journeys.Select(
journey => new SearchViewModel()
{
UserName = user.FirstName,
JourneyDestination = journey.JourneyDestination
}
)
)
.ToList();
if (!searchView.Any())
{
// no results logic
}
The second method would be even better if your repository returned IQueryable<User> instead of calling ToList() and returning IList<User>, BUT that won't work with your repository disposing the DbContext right away. As it stands (using ToList()), you're going to end up doing more processing in memory than you would if you let SQL do the work. If your repository knew about SearchViewModel, you could do this:
public IList<SearchViewModel> GetSearchViewModels()
{
using (var db = new EfDbContext())
{
var users = from user in db.Users
from journey in user.Journeys
select new SearchViewModel()
{
UserName = user.FirstName,
JourneyDestination = journey.JourneyDestination
}
select userJourney;
return users.ToList();
}
}
However, that may be an unacceptable mixing of presentation and data layers, depending on how strict your architecture is.
how best to integrate a method which contains a query that queries multiple entities into the repository.
Considering the relationship between User and Journey, you should decide which entity owns the relationship and use Aggregate Root to fetch data.
The description of Aggregate Root at What's an Aggregate Root? would be helpful.
Update:
Entities
public class User
{
public User()
{
this.Journeys = new List<Journey>();
}
public int Id { get; set; }
public virtual IList<Journey> Journeys { get; set; }
}
public class Journey
{
public Journey()
{
this.Users = new List<User>();
}
public int Id { get; set; }
public virtual IList<User> Users { get; set; }
}
Repositiories
public class UserRepository
{
public IList<User> GetAllUsersWithJourneys()
{
//Fetch all Users; Include Journeys
}
}
public class JourneyRepository
{
public IList<Journey> GetAllJourneysWithUsers()
{
//Fetch all journeys; Include Users
}
}
ViewModel
public class UserJourneyViewModel
{
public int UserId { get; set; }
public int JourneyId { get; set; }
}
Controller's Action
public ViewResult Search()
{
// Use UserRepository or JourneyRepository to make a list of
// UserJourneyViewModel that provides appropriate data for the view.
}

Categories

Resources