I have 2 tables in my entity framework:
INATIVOS (Employees)
EMPRESAS (Companies)
When registering an employee I select a company in a #Html.DropDownListFor (List).
The registration is ok, the company is saved correctly. However, when trying to edit a registered employee shows the error "Unable to set field/property on entity" in the Companies list.
INATIVO.cs
public partial class INATIVOS
{
public decimal ID { get; set; }
public string COD_EMPRESA { get; set; }
public string CHAPA { get; set; }
public string NOME { get; set; }
public System.DateTime DATA_NASC { get; set; }
public string PLANO { get; set; }
public short LEI { get; set; }
public short APOSENTADO { get; set; }
public short ESTADO_VIDA { get; set; }
public short ISENTO { get; set; }
public Nullable<System.DateTime> INICIO_VIGENCIA { get; set; }
public Nullable<System.DateTime> FIM_VIGENCIA { get; set; }
public string CPF { get; set; }
public string EMAIL { get; set; }
public string ENDERECO { get; set; }
public string NUMERO { get; set; }
public string COMPLEMENTO { get; set; }
public string BAIRRO { get; set; }
public string CIDADE { get; set; }
public string ESTADO { get; set; }
public string CEP { get; set; }
public string TELEFONE { get; set; }
public string CELULAR { get; set; }
public string OBSERVACAO { get; set; }
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
InativoController.cs
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
If these are navigation properties:
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Then (1) they need to be virtual and (2) they need to be something like IList or ICollection:
public virtual ICollection<DEPENDENTES> DEPENDENTES { get; set; }
public virtual ICollection<EMPRESAS> EMPRESAS { get; set; }
public virtual ICollection<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Though, as an aside, what you're doing here is very strange:
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
Essentially what you have in the database is, for a given Employee (INATIVOS) there are relationships to specific Companies (EMPRESAS) and specific Medical Plans (PLANOS_MEDICO). But you're ignoring whatever is in that data and replacing it with all companies and all medical plans in the entire database.
So every time you use this controller action to fetch an existing employee record, it's going to look like that employee has every company and every medical plan. Even though that's not what's in the database. I strongly suspect that's not what you want.
UPDATE: Based on comments on this answer, it sounds like those aren't navigation properties. They're not even properties of the model at all. They're just lists of data needed for the view to populate (presumably) <select> elements.
If they're not part of the data model then remove them from the model. Instead, consider using a view model. For example:
public class InativosViewModel
{
public INATIVOS Inativos { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
Then in the controller return an instance of the view model, which is a composite object of the model and the data needed for the view:
public ActionResult Index(int? id)
{
InativosViewModel result = new InativosViewModel();
using (Entidades db = new Entidades())
{
if (id != null)
{
result.Inativos = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
result.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
result.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(result);
}
And of course change the model binding in the view itself to now expect and use an instance of InativosViewModel. The resulting POST action can still accept an instance of INATIVOS if it needs to, or it can accept an instance of InativosViewModel just as well. That all depends on what the form structure is and what's being posted to that action.
Alternatively, if you want to keep using the INATIVOS model then still remove those lists from it but use something like ViewBag to send them to the view. Something like this:
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
ViewBag.Empresas = db.EMPRESAS.ToList<EMPRESAS>();
ViewBag.PlanosMedico = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
Then in your view you would populate the <select> elements from there:
#Html.DropDownListFor(
model => Model.COD_EMPRESA,
new SelectList(ViewBag.Empresas, "CODIGO", "DESCRICAO"),
htmlAttributes: new { #class = "form-control"
})
Related
I have an implicit one to many relationship set up between Template and TemplateParameters. As far as I can tell, I am following conventions. However, when I save a Template, the children are not discovered and saved with it.
public class Template
{
#region Properties
public int TemplateId { get; set; }
public string UserId { get; set; }
public DateTime Created { get; set; }
public DateTime Published { get; set; }
public bool Deleted { get; set; }
public int Likes { get; set; }
public int Shares { get; set; }
public int Downloads { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
#endregion
#region Navigation
//navigation properties
public ApplicationUser User { get; set; }
public IEnumerable<Wave> Waves { get; set; }
public IEnumerable<TemplateParameter> TemplateParameters;
public IEnumerable<ApplicationUser> Subscribers;
#endregion
}
public class TemplateParameter
{
public int TemplateParameterId { get; set; }
public int TemplateId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double? Value { get; set; }
public DateTime Created { get; set; }
//navigation properties
public virtual Template Template { get; set; }
}
[HttpPost]
[Route("SaveTemplate")]
public TemplateViewModel SaveTemplate([FromBody] TemplateViewModel vm)
{
//get the current userId
var user = _userManager.GetUserAsync(HttpContext.User).Result;
var token = User;
//map params
var parameters = vm.Parameters.Select(r => new TemplateParameter()
{
Name = r.Name,
Description = r.Description,
Created = DateTime.Now,
}).ToList();
//map the vm to the model
var template = new Template()
{
Name = vm.Name,
Description = vm.Description,
Created = DateTime.Now,
UserId = user.Id,
TemplateParameters = parameters,
};
//save the template
template = _templateService.SaveTemplate(template);
//map id back to the view model
vm.TemplateID = template.TemplateId;
return vm;
}
public Template SaveTemplate(Template template)
{
_context.Templates.Add(template);
_context.SaveChanges();
return template;
}
Can anyone spot what I'm doing wrong here? The template saves fine, but the child collections does not. I'm using the same pattern elsewhere in my project and it seems to work without issue. Thank you in advance for your help.
I am trying to create an endpoint to take the ID of the "manualMaster" and duplicate it & all of its children which reside inside of a tree like structure, each node has a parent which are all tied to the main parent being the ManualMaster.
Below are the models used
public class ManualStep
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Directions { get; set; }
public int MasterId { get; set; }
public ManualMaster Master { get; set; }
public int Index { get; set; } // This property governs where inside of the manual master this step belongs
public virtual ICollection<Image> Images { get; set; } // Virtual because I think I'd always like to have the attached image objects.
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
//Begin tree storage scheme
public int? ParentId { get; set; }
public ManualStep Parent { get; set; }
public ICollection<ManualStep> Nodes { get; set; }
}
public class ManualMaster
{
public int Id { get; set; }
public string Name { get; set; }
public int BrandId { get; set; }
public Brand Brand { get; set; }
public string Comment { get; set; }
public ICollection<ManualStep> Steps { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
}
And then the endpoint which takes the ID and attempts to duplicate.
// GET/DUPLICATE: api/Manual/5
[Route("{id}")]
[HttpGet]
[ResponseType(typeof(ManualMaster))]
public async Task<IHttpActionResult> DuplicateManualMaster(int id)
{
ManualMaster manualMaster = await db.ManualMasters
.Include(i => i.Steps)
.FirstOrDefaultAsync(i => i.Id == id);
//
if (manualMaster == null)
{
return NotFound();
}
var dup = new ManualMaster
{
Brand = db.Brands.FirstOrDefault(i => i.Id == manualMaster.BrandId),
BrandId = manualMaster.BrandId,
//manualMaster.Id,
Name = manualMaster.Name,
Comment = manualMaster.Comment
};
foreach (var step in manualMaster.Steps)
{
var newStep = step;
newStep.Parent.Id = dup.Id;
db.ManualSteps.Add(newStep);
await db.SaveChangesAsync();
}
db.ManualMasters.Add(dup);
await db.SaveChangesAsync();
return Ok(dup);
}
My problem is that currently when duplicating, I am setting the id of every node to the main parent ID and not to their respective ID's which create the tree structure.
newStep.Parent.Id = dup.Id;
So instead of copying the entire tree all of my nodes have the same ID... Instead I need to copy the tree structure how it currently is...
My News.cs class has a one to many relationship with Comment.cs as defined below
public class News
{
public int NewsId { get; set; }
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
public ICollection<Comment> Comment { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
public int? NewsId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
[ForeignKey("NewsId")]
public virtual News News { get; set; }
}
I have a controller action where i am trying to fetch one News item alongside all its comments so i set up two viewModels like this
public class CommentVM
{
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
}
public class NewsCommentsVM
{
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
public List<CommentVM> Comments { get; set; }
}
In my Controller action i have
public ActionResult Details(int? id)
{
UOW _unit = new UOW();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
News news = _unit.NewsRepository.GetByID(id);
if (news == null)
{
return HttpNotFound();
}
var model = new NewsCommentsVM()
{
Title = news.Title,
Details = news.Details,
DateCreated = news.DateCreated,
Author = news.AppUser.FirstName
Comments = news.Comment.Select(c => new CommentVM()
{
CommentText = c.CommentText,
Author = c.AppUser.Email,
DateCreated = c.DateCreated
}).ToList()
};
return View(result);
}
The problem is that the debugger is showing that Comment is returning Null whereas in the database there are related comments to that particular news item so i'm getting the error
Value cannot be null. Parameter: source
I've been able to use this code in another project without issues.
I think the problem is because you need to change the Comments collection property as virtual. If you want that related entities be lazy loaded, you need to follow this requirements:
public class News
{
//...
public virtual ICollection<Comment> Comment { get; set; }
}
Now,If you have disabled lazy loading, another option could be using the Include extension method in your query when you need to find a particular news:
int id=3;
var specificNews=context.News.Include(n=>n.Comment).FirstOrDefault(n=>n.Id==id);
This way the related entity will be included in the query result
I'm having a hard time trying to figure out how to bind data from the database to ViewModel. Basically, I have a domain model which I find has too much properties and which I'd like to reduce so logically I've chosen ViewModel to do so.
Domain model (automatically created from the database):
public partial class Ticket
{
public Ticket()
{
this.Daily = new HashSet<Daily>();
this.Ticket1 = new HashSet<Ticket>();
}
public int idTicket { get; set; }
public Nullable<int> idNadredeniTicket { get; set; }
public short RedniBroj { get; set; }
public int idFirma { get; set; }
public Nullable<int> idKontakt { get; set; }
public Nullable<int> idManager { get; set; }
public string Tip { get; set; }
public string Status { get; set; }
public Nullable<System.DateTime> DatumPrijave { get; set; }
public string VrstaPrijave { get; set; }
public string Prioritet { get; set; }
public Nullable<System.DateTime> DatumDo { get; set; }
public string Opis { get; set; }
public string Biljeske { get; set; }
public Nullable<bool> Zatvoren { get; set; }
public Nullable<bool> IzdanRacun { get; set; }
public Nullable<System.DateTime> DatumZatvaranja { get; set; }
public Nullable<int> idAsset { get; set; }
public virtual ICollection<Daily> Daily { get; set; }
public virtual Firma Firma { get; set; }
public virtual Kontakt Kontakt { get; set; }
public virtual Kontakt Kontakt1 { get; set; }
public virtual ICollection<Ticket> Ticket1 { get; set; }
public virtual Ticket Ticket2 { get; set; }
}
ViewModel:
public class OpenTickets
{
public int idTicket { get; set; }
public Nullable<int> idNadredeniTicket { get; set; }
public short RedniBroj { get; set; }
public int idFirma { get; set; }
public Nullable<int> idKontakt { get; set; }
public Nullable<int> idManager { get; set; }
public string Tip { get; set; }
public string Status { get; set; }
public Nullable<System.DateTime> DatumPrijave { get; set; }
public string VrstaPrijave { get; set; }
public string Prioritet { get; set; }
public string Opis { get; set; }
public string Biljeske { get; set; }
public string BrojTicketa
{
get
{
return idNadredeniTicket.ToString() + "-" + RedniBroj.ToString();
}
}
public string NazivTicketa
{
get
{
return BrojTicketa + " - " + Opis;
}
}
public string DetaljiTicketa
{
get
{
return Opis + "\r\n" + Biljeske;
}
}
}
What I'd like to accomplish is to bind data from the database via query to the ViewModel but, understandingly, I get errors regarding different types of objects passed to the View. I'm posting controller and view for the reference.
Controller
public ActionResult OpenTickets()
{
var openTickets = db.Ticket
.Where(t => t.idFirma == 1)
.Where(t => t.Zatvoren == false);
return View(openTickets.ToList());
}
View (some code is intentionally ommited for brevity)
#model IEnumerable<IDE3_CRM.ViewModels.OpenTickets>
<table>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Biljeske)</td>
<td>#Html.DisplayFor(modelItem => item.Opis)</td>
</tr>
}
</table>
I recommend wrapping your db calls in a Repository. In here, you can transform your database objects into view models. For example:
public ActionResult OpenTickets()
{
var openTickets = ticketRepo.GetOpenTickets();
return View(openTickets);
}
// Implementation of ITicketRepo
public IEnumerable<OpenTickets> GetOpenTickets()
{
return db.Ticket
.Where(t => t.idFirma == 1 && t.Zatvoren == false)
.Select(do => new OpenTickets
{
// Fill in view model properties from database object
});
}
Hrvach,
You can limit the data fields in the view itself and I think that maybe more efficient. That being said here is another approach you can take:
Create a list of type OpenTickets
Select the tickets you want
Loop over the selected tickets and add a new openTicket with the
properites you want to keep to the list of openTickets
return the list of open tickets
public ActionResult OpenTickets()
{
List<OpenTickets> openTicketList = new List<OpenTickets>();//create a list of openTickets
var Tickets = db.Ticket//select the tickets that you want
.Where(t => t.idFirma == 1)
.Where(t => t.Zatvoren == false);
foreach (var ticket in Tickets)//Loop over the tickets and create an openTicket out of each ticket then add the openTick to the openTicketList
{
OpenTickets openTicket = new OpenTickets();//create new OpenTickets object
openTicket.propery1 = ticket.propery1;//set each property of the openTicket equal to the property of the Ticket that you want to keep
openTicket.propery2 = ticket.propery2;
openTicket.propery3 = ticket.propery3;
openTicket.propery4 = ticket.propery4;
openTicketList.Add(openTicket);//add new OpenTickets object to the list
}
return View(openTicketList);
}
I hope this helps...Best wishes
Bill
So I have a model:
[Table("Site")]
public class Store : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public bool IsActive { get; set; }
public string Address { get; set; }
public string City { get; set; }
[Display(Name = "Province")]
public int ProvinceID { get; set; }
public string Postal { get; set; }
public string Phone { get; set; }
public int StoreNumber { get; set; }
public bool visible { get; set; }
public DateTime lastShift { get; set; }
}
The field lastShift is from a different table called "Shifts", how do I get it from that table?
EDIT: The lookup will have to be something like this:
select top 1 shiftDate as lastShift from [Shifts] where SiteID = Store.ID
This is how I load my data:
public class MyDbContext: DbContext {
public MyDbContext()
: base("name=DefaultConnection") {
}
public DbSet<UserAccount> UserAccounts { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<ProvinceModel> Provinces { get; set; }
}
And this is how I use it:
MyDbContext database = new MyDbContext();
var myStores = from database.Stores select ID;
EDIT according to your last edit, this is not the case
It will need to be a "navigation property" which means that you'll need to have an explicit (Foreing Key) relationship between Site and Ship
Then you'll have something like this
Store.Shift.LastShift
But if it is a one to many relationship (and LastShift field is not part of Shift table) then
you'll need to do it manually, and use a view model or a custom property that it is not mapped directly to the table and do the assignment somewhere in your code
If you're using a Repository, then you'll need to add a method there to get the last shift
Then (or if you are using ORM directly) you use the code that #Cristi posted, just remember to add the sorting
public ActionResult MyAction(){
var store = db.Stores.Where(x => x.ID == objId).Select(x => new StoreModel(){
Name = x.Name,
ID = x.ID,
lastShift = db.Shifts.FirstOrDefault(y => y.SiteID == x.ID).OrderByDescending(shift => shift.Date);
}).FirstOrDefault();
return View(store);
}
Here is how I solved the problem.
TL,DR: I shared my dbcontext in my controller so I have access to it in my models!
I manually load the lastShiftTime in my Store Model with the following code:
public class Store : MPropertyAsStringSettable {
.......
public DateTime lastShiftTime {
get{
MyDbContext curContext = MyWebProject.Controllers.MyBaseController.database;
if (curContext != null) {
return (from shift in curContext.Shifts where shift.SiteID == ID select shift.ShiftDate).First();
} else {
return new DateTime(0);
}
}
}
}
Here is my Shift Model I created for this, very standard:
[Table("Shifts")]
public class Shift : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int SiteID { get; set; }
public string ShiftID_In_POS { get; set; }
public DateTime ShiftDate { get; set; }
}
And here is how I am sharing the context in controller:
public class MyBaseController : Controller {
.........
private static MyDbContext _database;
public static MyDbContext database {
get {
if (_database == null) {
_database = new MyDbContext();
}
return _database;
}
}
}