Asp.net Mvc Model class as inherited Entity class - c#

I have extended a entity framework class (SQL server table) and added some extra properties to the child class but when I want to insert into the table which I already extended I get this exception:
Mapping and metadata information could not be found for EntityType 'Student.Models.Add.SubjectToStageModel'.
My controller:
[HttpPost]
public ActionResult SubjectToStage(SubjectToStageModel model)
{
try
{
if (ModelState.IsValid)
{
using (StudentEntities studentEntities = new StudentEntities())
{
int intCount =
studentEntities.SubjectToStageTbls.Count(
x => x.StageId == model.StageId && x.SubjectId == model.SubjectId);
if (intCount == 0)
{
studentEntities.SubjectToStageTbls.Add(model);
studentEntities.SaveChanges();
}
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
TempData["error"] = "An error occured";
}
return RedirectToAction("SubjectToStage");
}
My base class:
public partial class SubjectToStageTbl
{
public SubjectToStageTbl()
{
this.StudentMarkTbls = new HashSet<StudentMarkTbl>();
}
public int SubjectToStageId { get; set; }
public int SubjectId { get; set; }
public int StageId { get; set; }
public int Point { get; set; }
public virtual StageTbl StageTbl { get; set; }
public virtual ICollection<StudentMarkTbl> StudentMarkTbls { get; set; }
public virtual SubjectTbl SubjectTbl { get; set; }
}
My subclass:
public class SubjectToStageModel : SubjectToStageTbl
{
public IEnumerable<SelectListItem> StageListItem
{
get
{
List<SelectListItem> listsSelectListItems = new List<SelectListItem>();
try
{
using (StudentEntities studentEntities = new StudentEntities())
{
IQueryable<StageTbl> queryableStage = studentEntities.StageTbls;
foreach (var stage in queryableStage)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = stage.StageId.ToString();
selectListItem.Text = stage.StageName;
listsSelectListItems.Add(selectListItem);
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return listsSelectListItems;
}
}
public IEnumerable<SelectListItem> SubjectListItem
{
get
{
List<SelectListItem> listsSelectListItems = new List<SelectListItem>();
try
{
using (StudentEntities studentEntities = new StudentEntities())
{
IQueryable<SubjectTbl> queryableSubject = studentEntities.SubjectTbls;
foreach (var stage in queryableSubject)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = stage.SubjectId.ToString();
selectListItem.Text = stage.SubjectName;
listsSelectListItems.Add(selectListItem);
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return listsSelectListItems;
}
}
}

You didn't explicitly map the SubjectToStageModel class. If you want Entity Framework to work with derived classes you should add them to the model as well. But I don't think you intended to do that in the first place.
In fact, SubjectToStageModel is a view model. It may look convenient to derive a view model from an entity class, but I think generally it's not a good idea. View models should be tailored to the view (or use case) they're used in. A couple of reasons:
It's very likely that the entity class contains more properties than you need in the view. In later maintenance it's always a pain to keep checking what you do and don't need.
While view evolves, it may require a differently structured model than the entity.
The view may require different validations.
The view may be allowed to return a state that absolutely shouldn't be stored (you may require some post processing of entered data), so it's good to ensure it can't possibly be stored.
It creates a dependency between the data layer model and the view.
Maybe these consideration don't apply in your case. Still I'd prefer to have an independent view model. However, if you're lazy (we developers prefer the word pragmatic), you may succeed by doing:
studentEntities.SubjectToStageTbls.Add((SubjectToStageTbl)model);
(I never tried though).

Related

ObjectContext object error- EF

I have two tables written in EF CodeFirst:
public class DayType
{
[Key]
public int DayTypeID { get; set; }
public string NameDayType { get; set; }
public virtual ICollection<SpecialDay> Specialdays { get; set; }
public DayType() { }
}
public class SpecialDay
{
[Key]
public int SpecialDayID { get; set; }
public int Year { get; set; }
public int JanuaryDay { get; set; }
public SpecialDay() { }
public int? DayTypeId { get; set; }
public virtual DayType daytype { get; set; }
}
DBContext relation one-to-many were made by FluentAPI:
modelBuilder.Entity<DayType>().HasMany(p => p.Specialdays).WithOptional(p => p.daytype);
This code throw exception. The purpose of function is to update entity. While debugging sd had all properties. sd - is object which were selected from datagrid and then changed.
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.FirstOrDefault(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
....
newsd.DecemberDay = sd.DecemberDay;
newsd.DayTypeId = sd.DayTypeId;
newsd.daytype = sd.daytype;
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
At these point exception is happened _db.SaveChanges(); Exception:
{"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."} System.Exception {System.InvalidOperationException}
Would be thankful for any help with solution for my problems. Thank you
As the exception states,
"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
Looking at the code you have there, you are assigning the daytype from the one passed in to the one you pulled from the database. Because you're creating a new SalDBContext every time you call into the Update method, you're assigning the daytype across ObjectContext objects (per the error message.
To get around that, you just need to eliminate that assignment in your Update method. Because you're assigning the FK ID in the property, you do not also need to assign the object.
One other note on EF, there is also a Find method which will go just by the ID instead of FirstOrDefault. It's a bit more optimized.
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.Find(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
....
newsd.DecemberDay = sd.DecemberDay;
newsd.DayTypeId = sd.DayTypeId;
// newsd.daytype = sd.daytype; Must be eliminated!
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
You have loaded sd (from method head) with anonther context as the one in your method body newsd. Therefore you received an exception on calling _db.SaveChanges();. I would say in line newsd.daytype = sd.daytype;, because it's a complex data type and represents one of your entities. Remove the line and set only the ID or load the obejct sd again for referencing.
Modified version of yours:
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.FirstOrDefault(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
// ...
newsd.DecemberDay = sd.DecemberDay;
// set only the id to reference the object
newsd.DayTypeId = sd.DayTypeId;
// newsd.daytype = sd.daytype;
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
Well I was some seconds to late. :)

Copying data between models and saving children without entities duplicating themselves in Entity Framework

I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.

Return ViewModel instead of Model

The following method returns a IEnumerable MODEL called PROF.
Instead of using the model, I want to use a viewModel which contains the same fields as the Model PROF. How can i make changes to the following function to return my VIEWMODEL(which is called MyProfViewModel ) instead of the model (which is called PROF ).
public async Task<ActionResult> Index()
{
var cp= db.PROF.Include(c => c.ACCOUNTS);
return View(await cp.ToListAsync());
}
ViewModel
public class MyProfViewModel
{
public int myprofID { get; set; }
public string myprofDes{ get; set; }
}
Model
public class PROF
{
public int ID{ get; set; }
public string DESCRIPTION { get; set; }
}
Joel's answer is pretty much it. But since you mentioned you are starting I will provide a more deltailed answer to make more clear how to use.
First you define a conversion, in this case I put it in the ViewModel, but you can put it somewhere else that would make more sense in your project:
public class MyProfViewModel
{
public int myprofID { get; set; }
public string myprofDes { get; set; }
public static MyProfViewModel FromModel(PROF model)
{
var viewModel = new MyProfViewModel()
{
myprofID = model.ID,
myprofDes = model.DESCRIPTION
};
return viewModel;
}
}
Then you just apply the conversion before returning:
public async Task<ActionResult> Index()
{
var cp = PROF.Include(c => c.ACCOUNTS);
var models = await cp.ToListAsync();
var viewModels = models.Select(MyProfViewModel.FromModel);
return View(viewModels);
}
By the way, you can make the tranformation to models as well by adding this to your ViewModel class:
public PROF ToModel()
{
return new PROF()
{
ID = this.myprofID,
DESCRIPTION = this.myprofDes
};
}
Hope this helps!
Add a select statement which maps one type to another:
db.PROF.Include(c => c.ACCOUNTS)
.Select(x=> new MyProfViewModel(){myprofID = x.ID, ...});
Depending on if this select statement is supported by your ORM layer (Entity Framework?), I would do it before, or after .ListAsync();
Pros of before: could lead to better optimized SQL queries.

How to update a collection inside an entity within a post action in ASP.NET MVC5?

I have created an ASP.NET MVC5 sample project. I created my entities and from that, scaffolded the controllers for CRUD operations. I can only edit the POD members with the scaffolded code. I want to be able to add/remove related entities.
With my current code, when I click save there is no error but no related entities are modified (POD data is modified though). For example, if I wanted to remove all players from the account, they aren't removed. What am I doing wrong?
How can I remove/add related entities and push those changes to the database?
Here is the form:
Here is the action to update the entity:
public async Task<ActionResult> Edit([Bind(Include = "Account,Account.AccountModelId,Account.Name,Account.CreatedDate,SelectedPlayers")] AccountViewModel_Form vm){
if (ModelState.IsValid){
if (vm.SelectedPlayers != null){
vm.Account.PlayerModels = db.PlayerModels.Where(p => p.AccountModel.AccountModelId == vm.Account.AccountModelId).ToList();
foreach (var player in vm.Account.PlayerModels){
player.AccountModel = null;
db.Entry(player).State = EntityState.Modified;
}
vm.Account.PlayerModels.Clear();
foreach (var player_id in vm.SelectedPlayers){
var player = db.PlayerModels.Where(p => p.PlayerModelId == player_id).First();
vm.Account.PlayerModels.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}
db.Entry(vm.Account).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vm);
}
Here are the models:
public class AccountViewModel_Form{
public AccountModel Account { get; set; }
public HashSet<Int32> SelectedPlayers { get; set; }
public virtual List<PlayerModel> PlayersList { get; set; }
}
public class AccountModel{
public AccountModel(){
PlayerModels = new HashSet<PlayerModel>();
}
public Int32 AccountModelId { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<PlayerModel> PlayerModels { get; set; }
}
public class PlayerModel{
public Int32 PlayerModelId { get; set; }
public float Gold { get; set; }
public string Name { get; set; }
public virtual AccountModel AccountModel { get; set; }
}
I'm basically lost. I can't find any examples in how to update related data. Could someone point me in the right direction?
I come from Symfony (PHP Framework) background. I thought it would be easier but I have been having problems.
Basically I was missing the Attach function and that I had to force the load on the collection to make it work.
I found how to attach a non-attached entity here: Model binding in the controller when form is posted - navigation properties are not loaded automatically
When you post the data, the entity is not attached to the context, and when you try to save changes to a complex entity, the context makes a mess.
The code is a little different because I was trying to make it work at home. But it is essentially the same models.
public ActionResult Edit(AccountEditViewModel vm)
{
if (ModelState.IsValid)
{
//I was missing these 2 important lines...
db.Accounts.Attach(vm.Account);
db.Entry(vm.Account).Collection(a => a.Players).Load();
if (vm.SelectedPlayers != null)
{
foreach (var player in vm.Account.Players.ToList())
{
if (vm.SelectedPlayers.Contains(player.Id) == false)
{
player.Account = null;
vm.Account.Players.Remove(player);
db.Entry(player).State = EntityState.Modified;
vm.SelectedPlayers.Remove(player.Id);
}
}
foreach (var player_id in vm.SelectedPlayers)
{
var player = db.Players.Where(p => p.Id == player_id).First();
player.Account = vm.Account;
vm.Account.Players.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}else
{
vm.Account.Players.Clear();
}
db.Entry(vm.Account).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}

ASP.NET MVC model binding - nested properties

I have a model:
public class DataModel
{
public GridSortOptions SortOptions { get; set; }
public string Term { get; set; }
public int? Page { get; set; }
...
}
public class GridSortOptions
{
public string Column { get; set; }
public SortDirection Direction { get; set; }
}
And a http request: ?Column=LastName&Direction=Descending
That request will not work, i need to update it as follow: ?SortOptions.Column=LastName&SortOptions.Direction=Descending
Is it possible to teach ASP.NET to understand the original request without changing the model?
PS. I know that I could create a custom binder but I feel that there is a much simple way exist...
Thanks in advance!
I have mine structured slightly differently, as I came across the same problem (in MVC1 and still have it now - mvc4) . I have often found that having nested models tend to cause headaches and unnecessary complexity, so I try to flatten my view models as much as possible, just to simplify things, so don't have to write custom model binders, saves a lot of time and code.
My action typically looks method looks like this
//...snip
public ActionResult List(int? page, GridSortOptions sortOptions, string keyword) {
var model = new UserGridViewModel();
IQueryable<User> users = new UserRepository(Session).List();
if (sortOptions.Column != null) {
users = users.OrderBy(sortOptions.Column, sortOptions.Direction);
}
if (keyword != null) {
users = users.Where(x => x.Name.Contains(keyword))
}
model.SortOptions = sortOptions;
//using MvcContrib.Pagination.PaginationHelper here
model.Results = users.AsPagination(page ?? 1, 20);
return View(model);
}
//.....
My view logic is simply:
#using MvcContrib.UI.Grid
#using MvcContrib.UI.Pager
#model UsersGridViewModel
#Html.Grid(Model.Results).Columns(cols => {
cols.For(col => col.Id).Sortable(true);
cols.For(col => col.Name).Sortable(true);
//...etc
}).Sort(Model.SortOptions)
My grid view models are normally like this:
public class UserGridViewModel
{
public IPagination<User> Results { get; set; }
public GridSortOptions SortOptions { get; set; }
}

Categories