Asp.net Mvc display list depending on User - asp.net-mvc

What i have initially on my App is a list of expensives that is created based on the Scarfolding System but that list is the same for each User, and what i want is that each user can create his own list of expensives and see his own data.
So in the expensive class i did this:
public class Despesa
{
public int TipoDespesaId { get; set; }
public int DespesaId { get; set; }
public string UserId { get; set; }
[Display(Name = "Descrição da Despesa")]
[Required]
public string DespesaDescricao { get; set; }
[Display(Name = "Valor")]
[Required]
public decimal DespesaValor { get; set; }
public int TipoPagamentoId { get; set; }
[Display(Name = "Data")]
[DataType(DataType.Date)]
[CustomValidation(typeof(Validator), "ValidateEndTimeRange")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",ApplyFormatInEditMode = true)]
[Required]
public DateTime Data { get; set; }
public TipoDespesa TipoDespesa { get; set; }
public TipoPagamento TipoPagamento { get; set; }
[Display(Name = "Comentário")]
public string Comentario { get; set; }
}
i just passed the UserId to the model and then in the Index controller of my Expensive View i did a linq query to compare the currentUserID to the Id of the expensive User here is my code:
public ActionResult Index()
{
String userId = User.Identity.GetUserId();
var despesas = from r in db.Despesas.Include(d => d.TipoDespesa).Include(d => d.TipoPagamento).Include(d => d.UserId)
where r.UserId.Equals(userId)
select r;
return View(despesas.ToList());
}
what i need to know is what i am doing wrong cause i get a invalidOperationException

Only navigation properties can be used with .Include() it seems.
You are trying to include a primitive property (UserId), and it then throws the error when converting to a list because it has no navigation property.
var despesas = from r in db.Despesas.Include(d => d.TipoDespesa).Include(d => d.TipoPagamento).Where(x => x.UserId == userId) select r;

Related

NullReferenceException in a three table model in Entity Framework

I'm trying to join three tables in a view model. It works with two tables but crashes when I add a third. Here are the models and the controller. The models section_detail, phone, and department were generated by Entity Framework.
EmployeeViewModel was created by copying properties from the other models. I've abbreviated some of the models shown here with:
public partial class section_detail
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public virtual department department { get; set; }
public virtual phone phone { get; set; }
}
public partial class phone
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public phone()
{
this.section_detail = new HashSet<section_detail>();
}
public int phone_id { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
. . .
public string activity_code { get; set; }
public string function_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
public virtual BudgetUnit BudgetUnit { get; set; }
}
public partial class department
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public department()
{
this.section_detail = new HashSet<section_detail>();
}
public int dept_id { get; set; }
public string description { get; set; }
public string cost_center_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
}
public class EmployeeViewModel
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
public Nullable<byte> page_code { get; set; }
public string cost_center_code { get; set; }
public string print_descrip { get; set; }
public Nullable<int> phone_id { get; set; }
public Nullable<int> employee_id { get; set; }
public static explicit operator EmployeeViewModel(List<section_detail> v)
{
throw new NotImplementedException();
}
public string first_name { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public string description { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
public string BU { get; set; }
}
Controller:
private vcpds_test1Entities db = new vcpds_test1Entities();
// GET: EmployeeList
public ActionResult Index()
{
List<section_detail> employeeList = db.section_detail.ToList();
List<EmployeeViewModel> employeeVMList = employeeList.Where(emp => emp.page_code == 3)
.Select(emp => new EmployeeViewModel
{
last_name = emp.last_name,
first_name = emp.first_name,
employee_id = emp.employee_id,
phone_nbr = "(" + emp.phone.area_code + ") " + emp.phone.phone_nbr.Substring(0, 3) + "-" + emp.phone.phone_nbr.Substring(3, 4),
BU = emp.phone.BU,
description = emp.department.description,
page_code = emp.page_code
}).OrderBy(emp => emp.last_name).ThenBy(emp => emp.first_name).ToList();
return View(employeeVMList);
}
I get these messages:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
VCPDS2.Models.section_detail.department.get returned null.
If I comment out description = emp.department.description from the controller, then it will return data from the section_detail and phone tables. I've checked the database and the relationships seem ok. I've tried refreshing the models from the database with no change.
It's possible that a emp doesn't have a department so it in itself is null. Description can't be a property of a null. So, what you can simply do is check if it is null first by using null operator:
...
//description = emp.department.description,
description = emp.department?.description ?? "",
...
Basically, if department itself is null, it will stop checking right there, and the ?? shortcut is to use the statement on the right side which is "" if the statement on the left is null.
If you were not expecting an emp not to have a department, you may need to revise your query
Quick edit: You probably need to use an Include in your query so it can bring the department's properties (for description):
List<section_detail> employeeList = db.section_detail
.Include(x => x.department)
.ToList();

ViewModel for multiple tables without links between tables

I'm trying to write a View Model in an ASP.NET MVC5 project to show data from different tables on a form that the user can then edit and save. I'm using the following :-
VS 2017, c#, MySQL Database, Entity Framework 6.1.3, MySQL Connector 6.9.9, Code First from Database (existing database that I can't change)
To complicate matters, there are no links between the tables in the database, so I cannot work out how to create a suitable View Model that will then allow me to save the changes.
Here are the 4 table models :-
public partial class evc_bearer
{
[Key]
[Column(Order = 0]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long evcid { get; set; }
[Key]
[Column(Order = 1]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long bearerid { get; set; }
public int vlan { get; set; }
[Column("ref")]
public string _ref { get; set; }
[Required]
public string port { get; set; }
[Required]
public string endpoint { get; set; }
}
public partial class bearer
{
[Key]
public long id { get; set; }
[Column("ref")]
[Required]
public string _ref { get; set; }
public string name { get; set; }
public long? site { get; set; }
public long? provider { get; set; }
public long? mtu { get; set; }
public float? rental { get; set; }
public int? offsiteprovider { get; set; }
public float? offsiteproviderrental { get; set; }
public bool? aend { get; set; }
public int? equipmentport { get; set; }
public string orderref { get; set; }
public string offsiteref { get; set; }
public string notes { get; set; }
public float? bookingfactor { get; set; }
}
public partial class evc
{
[Key]
public long id { get; set; }
[Required]
public string type { get; set; }
public byte servicetype { get; set; }
public byte cos { get; set; }
public int cir { get; set; }
public int pir { get; set; }
public bool burst { get; set; }
[Column("ref")]
public string _ref { get; set; }
public string orderref { get; set; }
public byte state { get; set; }
public string notes { get; set; }
public float? rental { get; set; }
}
public partial class evc_provider
{
[Key]
public long id { get; set; }
[Required]
public string provider { get; set; }
}
This is the View Model I tried writing :-
public partial class evcBearersVM
{
[Key]
[Column(Order = 0)]
public long evcid { get; set; }
[Key]
[Column(Order = 1)]
public long id { get; set; }
[Column("ref")]
public string b_ref { get; set; }
public string b_name { get; set; }
public string ep_provider { get; set; }
public int eb_vlan { get; set; }
public string eb_port { get; set; }
public string eb_endpoint { get; set; }
}
This is the Linq query I used to populate the View Model :-
IQueryable<evcBearersVM> data = from eb in csdb.evc_bearers
join b in csdb.bearers on eb.bearerid equals b.id
join ep in csdb.evc_providers on b.provider equals ep.id
join e in csdb.evcs on eb.evcid equals e.id
where (eb.evcid == evcid && b.id == id)
select new evcBearersVM
{
evcid = eb.evcid,
id = b.id,
b_ref = b._ref,
b_name = b.name,
ep_provider = ep.provider,
eb_vlan = eb.vlan,
eb_port = eb.port,
eb_endpoint = eb._ref
};
So the query works and joins the tables to get the data I need and I can display this date in various views as needed. What I now need to do is be able to edit a row and save it back to the database. I have an Edit View that is showing the data I need but I'm not sure how to save changes given that it's a View Model and the DB Context isn't aware of it. Grateful for any help.
What you should do is, use the same view model as your HttpPost action method parameter and inside the action method, read the entities you want to udpate using the Id's (you can get this from the view model, assuming your form is submitting those) and update only those properties you need to update.
[HttpPost]
public ActionResult Edit(evcBearersVM model)
{
var b = csdb.bearers.FirstOrDefault(a=>a.id==model.id);
if(b!=null)
{
b.name = model.b_name;
b._ref = model.b_ref;
csdb.SaveChanges();
}
// Update the other entity as well.
return RedirectToAction("Index");
}

How do I pass column values from multiple tables to the view in MVC?

I'm displaying the data from a table called gigs, however it contains a couple of foreign keys to tables 'Bands' and 'Venues' so when using this code in my controller,
string user = User.Identity.GetUserId();
var yourgigs = (from g in dbg.gigs
from v in dbg.Venues
from b in dbg.Bands
from ga in g.gigsaccasses
where (ga.Id == user &&
v.venueid == g.venueid &&
b.bandid == g.bandid)
select g);
return View(yourgigs);
it's displaying bandid and venueid in the view which are meaningless integers. How would I replace those with what I suppose would be b.bandname, v.venuename and also add v.address1 and v.city? The SQL statement that does this is
SELECT bands.bandname, venues.venuename, venues.address1, venues.city, gigs.whatdate, gigs.starttime
FROM gigs INNER JOIN
bands ON gigs.bandid = bands.bandid INNER JOIN
gigsaccass ON gigs.gigid = gigsaccass.gigid INNER JOIN
dbo.AspNetUsers ON gigsaccass.Id = dbo.AspNetUsers.Id INNER JOIN
venues ON gigs.venueid = venues.venueid
WHERE dbo.AspNetUsers.Id = //some user//
I did try using anonymous types as such:
var yourgigs = (from g in dbg.gigs
from v in dbg.Venues
from b in dbg.Bands
from ga in g.gigsaccasses
where (ga.Id == user &&
v.venueid == g.venueid &&
b.bandid == g.bandid
select new
{
bandname = b.bandname,
venuename = v.venuename,
address1 = v.address1,
city = v.city,
whatdate = g.whatdate,
starttime = g.starttime
});
But this then threw an error:
The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType76[System.String,System.String,System.String,System.String,System.DateTime,System.TimeSpan]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[OnStageTonight_MVC.Models2.gigs]'.
The View is expecting type 'gigs'
#model IEnumerable<OnStageTonight_MVC.Models2.gigs>
#{
ViewBag.Title = "Gigs";
}
<h2>Gigs</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.venueid)
</th>
<th>
#Html.DisplayNameFor(model => model.bandid)
</th>
What am I missing?
EDIT:
I should add that I do have a model, but I'm assuming this is what is at fault.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace OnStageTonight_MVC.Models2
{
[Table("gigs")]
public partial class gigs
{
public gigs()
{
this.gigsaccasses = new HashSet<gigsaccass>();
}
[Key]
public int gigid { get; set; }
public int venueid { get; set; }
public int bandid { get; set; }
[Display(Name="Date")]
public System.DateTime whatdate { get; set; }
[Display(Name="Starts at")]
public System.TimeSpan starttime { get; set; }
public virtual ICollection<gigsaccass> gigsaccasses { get; set; }
}
[Table("gigsaccass")]
public partial class gigsaccass
{
[Key]
public int gigaccassid { get; set; }
public int gigid { get; set; }
public string Id { get; set; }
public virtual gigs gig { get; set; }
public virtual AspNetUsers AspNetUser { get; set; }
}
[Table("dbo.AspNetUsers")]
public class AspNetUsers
{
[Key]
public string Id { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public bool TwoFactorEnabled { get; set; }
public Nullable<System.DateTime> LockoutEndDateUtc { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
public string UserName { get; set; }
public string YourName { get; set; }
public List<gigsaccass> gigsaccasses { get; set; }
}
[Table("venues")]
public partial class venues
{
[Key]
public int venueid { get; set; }
[Required]
[Display(Name = "Venue")]
public string venuename { get; set; }
[Required]
[Display(Name = "Address")]
public string address1 { get; set; }
[Required]
[Display(Name = "City")]
public string city { get; set; }
public List<gigs> venuegigs { get; set; }
}
[Table("bands")]
public class bands
{
[Key]
public int bandid { get; set; }
[Required]
[Display(Name = "Name")]
public string bandname { get; set; }
public List<gigs> bandgigs { get; set; }
}
public partial class gigscontext : DbContext
{
public gigscontext()
: base("DefaultConnection")
{
}
public DbSet<gigs> gigs { get; set; }
public DbSet<gigsaccass> gigsaccass { get; set; }
public DbSet<AspNetUsers> AspNetUsers { get; set; }
public DbSet<venues> Venues { get; set; }
public DbSet<bands> Bands { get; set; }
}
}
You can't use anonymous classes here. Your view needs to know how to work with model, it needs type information, but: "The type name is generated by the compiler and is not available at the source code level"
You can't pass such objects around. http://www.codeproject.com/Articles/15624/Inside-C-Anonymous-Methods#5
You need to create class that represent row in your data set and return list of populated objects.
As less preferred alternative you can use dynamic: https://msdn.microsoft.com/en-us/library/dd264736.aspx
The M in MVC stands for model, and what you want is one of the 3 main tenants of MVC.
You want an object that encapsulates all the information to be displayed on the view.
It is considered best practice to create a model for the view. This is an additional layer which separates the storage of the item (your entity model) from it's presentation.
var yourgigs = (from g in dbg.gigs
from v in dbg.Venues
from b in dbg.Bands
from ga in g.gigsaccasses
where (ga.Id == user &&
v.venueid == g.venueid &&
b.bandid == g.bandid
select new GigViewModel
{
bandname = b.bandname,
venuename = v.venuename,
address1 = v.address1,
city = v.city,
whatdate = g.whatdate,
starttime = g.starttime
});
public class GigViewModel
{
public string bandname { get; set; }
public string venuename { get; set; }
public string address1 { get; set; }
public string city { get; set; }
public DateTime whatdate { get; set; }
public Timespan starttime { get; set; }
}
When persisting data from your view models, use AutoMapper or something similar to copy properties with matching names between your view models and your entity models.
Use the new view model in your view:
#model IEnumerable<OnStageTonight_MVC.Models2.GigModelView>

AutoMapper and required fields

I am trying to create a new record using only some of the fields in my Domain Model. I have created a ViewModel for this and am using AutoMapper.
My code is failing at the minute due to the required fields that I have on my Domain Model. I have added opt=>opt.Ignore() on the necessary field however, I am still having problems.
When I remove [Required] from the StaffPresent field the record is added to the DB.
In my Global.asax.cs
Mapper.CreateMap<CustomerSupportRecord, CustomerSupportRecordForCreation>();
Mapper.CreateMap<CustomerSupportRecordForCreation, CustomerSupportRecord>().ForMember(p=>p.StaffPresent, opt=>opt.Ignore());
Domain Model
public class CustomerSupportRecord
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Departure")]
public DateTime? DepartureTime { get; set; }
[Required(ErrorMessage = "Please select a Type")]
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Required(ErrorMessage = "Please enter the staff who were present at the Feedback")]
[Display(Name = "Staff Present at Feedback")]
public string StaffPresent { get; set; }
[Display(Name = "Setting")]
public string ReflectionSetting { get; set; }
[Display(Name = "Advisor")]
public string ReflectionAdvisor { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
// Navigation Properties
public virtual Customer Customer { get; set; }
public virtual CustomerSupportType CustomerSupportType { get; set; }
public virtual Employee Employee { get; set; }
}
ViewModel
public class CustomerSupportRecordForCreation
{
public int CustomerSupportRecordID { get; set; }
public int CustomerID { get; set; }
public string EmployeeID { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
}
And finally my Controller
//
// GET: /CustomerSupport/CustomerSupportRecord/Create
public ActionResult Create()
{
ViewBag.CustomerSupportTypeID = new SelectList(db.CustomerSupportType, "CustomerSupportTypeID", "CustomerSupportTypeName");
var model = new CustomerSupportRecordForCreation { CustomerID = 1, EmployeeID = "20213" };
return View("Create", model);
}
//
// POST: /CustomerSupport/CustomerSupportRecord/Create
[HttpPost]
public ActionResult Create(CustomerSupportRecordForCreation customersupportrecord)
{
if (ModelState.IsValid)
{
var newRecord = Mapper.Map<CustomerSupportRecordForCreation, CustomerSupportRecord>(customersupportrecord);
db.CustomerSupportRecord.Add(newRecord);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerSupportTypeID = new SelectList(db.CustomerSupportType, "CustomerSupportTypeID", "CustomerSupportTypeName", customersupportrecord.CustomerSupportTypeID);
return View(customersupportrecord);
}
AutoMapper's configuration doesn't have anything to do with validation attributes, it just specifies the rules for how to map objects between each-other.
In your case, the mapping:
Mapper.CreateMap<CustomerSupportRecordForCreation, CustomerSupportRecord>().ForMember(p=>p.StaffPresent, opt=>opt.Ignore());
tells AutoMapper not to copy the StaffPresent property.
If your database model has a [Required] attribute, you'll still need to set that data elsewhere.
One thing to note based on your comment, it's a good idea to add validation to your ViewModels, as it prevents you from calling your database methods with improper data, gives you client side validation, and lets you enforce different constraints than your model might require.

PagedList in MVC3 with IQueryable

I can't understand what i'm doing wrong. Every time I'm getting this error:
The entity or complex type 'BusinessLogic.CompanyWithDivisionCount' cannot be constructed in a LINQ to Entities query.
I need to get info from 'Company' table and divisions count of each company from 'Division' table, and then make PagedList. Here is my 'Company' table:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using BusinessLogic.Services;
using BusinessLogic.Models.ValidationAttributes;
namespace BusinessLogic.Models
{
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
}
Here is my domain model:
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
public class CompanyWithDivisionCount: Company // I'm using this
{
public int DivisionCount { get; set; }
}
Here is my controller:
public ActionResult CompaniesList(int? page)
{
var pageNumber = page ?? 1;
var companies = companyService.GetCompaniesWithDivisionsCount2();
var model = companies.ToPagedList(pageNumber, PageSize);
return View(model);
}
And here is my service part:
public IQueryable<CompanyWithDivisionCount> GetCompaniesWithDivisionsCount2()
{
return (from c in dataContext.Companies.AsQueryable()
select new CompanyWithDivisionCount
{
Id = c.Id,
Name = c.Name,
Status = c.Status,
EffectiveDate = c.EffectiveDate,
URL = c.URL,
EAP = c.EAP,
EAPCredentials = c.EAPCredentials,
Comments = c.Comments,
DivisionCount = (int)dataContext.Divisions.Where(b => b.CompanyName == c.Name).Count()
});
}
}
Thanks for help!!!
Creator of PagedList here. This has nothing to do with PagedList, but rather is an Entity Framework issue (I'm no expert on Entity Framework, so can't help you there). To confirm that this is true, write a unit test along the following lines:
[Test]
public void ShouldNotThrowAnException()
{
//arrange
var companies = companyService.GetCompaniesWithDivisionsCount2();
//act
var result = companies.ToList();
//assert
//if this line is reached, we win! no exception on call to .ToList()
}
I would consider changing you data model if possible so that instead of relating Companies to Divisions by name strings, instead use a properly maintained foreign key relationship between the two objects (Divisions should contain a CompanyID foreign key). This has a number of benefits (including performance and data integrity) and will almost certainly make your life easier moving forward if you need to make further changes to you app (or if any company ever decides that it may re-brand it's name).
If you create a proper foreign key relationship then your domain model could look like
public class Company
{
...
public virtual ICollection<Division> Divisions{ get; set; }
public int DivisionCount
{
get
{
return this.Divisions.Count()
}
}
...
}

Resources