ASP.NET MVC: Need help organizing data structure classes - asp.net-mvc-4

I come from a PHP/MySQL background, so maybe the problems I'm having with .NET stem from that. After a week of posting how to create an inventory tracker for our computer hardware and software, someone finally told me that he thought I was doing things wrong. The issue was not my code, but my design. That's certainly possible. I'm trying to do this in EF Code First and the idea of generating a database with code is foreign to me. However, I got the database working and everything was pointing to the right thing. But I can't pull what I need from the database.
What I want to do is create a dashboard page that would have categories for the types of hardware. So there would be a list of PCs, a list of monitors, a list of printers, etc. Initially, what I did was based on my knowledge of MySQL. I created a Hardware table (class) and a HardwareTypes table (class). In MySQL, what I would have done is put the ID for the HardwareType in the Hardware table, so I could do joins. Then I can get all of my PCs with a simple inner join.
.NET seems like it's different. It wants to create an intermediate table -- HardwareHardwareTypes, and then connect the two other tables. That seems strange, but OK. But when I go to get all of my PCs, I can't seem to get the help I need to write the query. So please take a look at my query and my classes, and let me know your thoughts.
Query, which returns Hardware, not HardwareTypes -- how do I get HardwareTypes?):
var pcs = db.Hardware.Where(h => h.HardwareType.Any(hwt => hwt.HType == "PC"));
ViewBag.Pcs = pcs.ToList();
Hardware class:
public class Hardware
{
public int Id { get; set; }
public virtual ICollection<DeviceType> Type { get; set; }
public string AssetTagId { get; set; }
public virtual ICollection<Manufacturer> Manufacturer { get; set; }
[Required]
[StringLength(50)]
public string ServiceTagId { get; set; }
[Required]
[StringLength(50)]
public string SerialNumber { get; set; }
[Required]
[StringLength(75)]
public string ProductNumber { get; set; }
// [Required]
[StringLength(20)]
public string PurchaseDate { get; set; }
[StringLength(20)]
public string WarrantyExpiration { get; set; }
[Required]
[StringLength(20)]
public string WarrantyType { get; set; }
public virtual ICollection<Location> Location { get; set; }
public virtual ICollection<HardwareType> HardwareType { get; set; }
[Required]
[StringLength(2000)]
public string Notes { get; set; }
public string POATag { get; set; }
}
HardwareTypes class:
public class HardwareType
{
public int Id { get; set; }
[Required]
[StringLength(128)]
public string HType { get; set; }
public virtual ICollection<Hardware> Hardware { get; set; }
}
Again, if what I need is more of a high-level design change, please let me know. If I need a different query, let me know that. The third (intermediate) table is dynamically generated and it's hard to know how to post that. I'd appreciate any and all help with this. What I need in the end is a list of PCs. Here is some sample seed data:
... new Hardware { AssetTagId = "2134",
Type = device.Where(h => h.DType == "Network Device").ToArray(),
Manufacturer = manuf.Where(h => h.ManufacturerName == "SonicWall").ToArray(),
ServiceTagId = "5243",
SerialNumber = "3456",
ProductNumber = "2345",
PurchaseDate = "2012-10-23",
WarrantyExpiration = "2012-11-12",
WarrantyType = "NBD",
Location = loc.Where(h => h.LocationName == "Paradise Lane").ToArray(),
Notes = "Scrapped",
HardwareType = htype.Where(h => h.HType == "PC").ToArray()}, ...
var htype = new List<HardwareType> {
new HardwareType { HType = "PC" },
new HardwareType { HType = "Monitor" },
new HardwareType { HType = "Printer" },
new HardwareType { HType = "Miscellaneous" }
};
If my seed data is structured wrong, please let me know that. Thanks.

Let me tell you first of all that the same database design which works in PHP/MySQL can work here also.
The easiest approach I will suggest to you is to create a view in the database which joins table Hardware and HardwareType, add nwly created view to you database model and fetch the desired data from the view instead of tables.

Related

Linq takes more than 20 seconds to query a table with less than 100 records

Unfortunately I haven't found a good answer for this problem yet. The answers and questions I have seen so far in here are about big tables with a lot of records.
I'm trying to query a table called Tickets with the following code:
var Status = ticketStatusService.GetByName("New");
string StatusID = Status.Id;
var tickets = db.Tickets.Where(e =>
!e.Deleted &&
e.Project == null &&
e.Status != null &&
e.Status.Id == StatusID);
var list = tickets.ToList();
The table currently has less than 100 records, this query takes an average of 22 seconds to execute.
The code first model for it is as follows:
public class Ticket : Base
{
[Key]
[Required]
public Guid Id { get; set; }
[Display(Name = "Date")]
public DateTime RowDate { get; set; } = DateTime.Now;
public bool Deleted { get; set; } = false;
[Index(IsUnique = true)]
public int? Number { get; set; }
[Display(Name = "Ticket Subject")]
public string Subject { get; set; }
[Display(Name = "Notes (Employees Only)")]
public string Notes { get; set; }
[Display(Name = "E-Mail")]
public string From { get; set; }
[Display(Name = "Phone Number")]
public string Phone { get; set; }
[Display(Name = "Secondary Phone Number")]
public string PhoneAlt { get; set; }
[Display(Name = "Client Name")]
public string Name { get; set; }
[Display(Name = "Message")]
public string Messages { get; set; }
[DataType(DataType.DateTime)]
public DateTime? OpenDate { get; set; }
[DataType(DataType.DateTime)]
public DateTime? CloseDate { get; set; }
[DataType(DataType.DateTime)]
public DateTime? AssignedDate { get; set; }
public bool? Origin { get; set; }
public virtual User AssignedUser { get; set; }
public virtual List<TicketFile> TicketFiles { get; set; }
public virtual List<Task> Tasks { get; set; }
public virtual Project Project { get; set; }
public virtual TicketStatus Status { get; set; }
public virtual TicketClosingCategory TicketClosingCategory { get; set; }
public virtual TicketGroup TicketGroup { get; set; }
public virtual TicketPriority TicketPriority { get; set; }
}
Any insight into this issue would be appreciated. Thank you very much!
Edit: Running the same query directly on SQL Server Management Studio also takes very long, about 9 to 11 seconds. So there might be an issue with the table itself.
I see several possible improvements.
For some reason you chose to deviate from the entity framework code fist conventions. One of them is the use of a List instead of an ICollection, another it that you omit to mention the foreign keys.
Use ICollection istead of List
Are you sure that Ticket.TicketFiles[4] has a defined meaning? And what would Ticket.TicketFiles.Insert(4, new TicketFile()) mean?
Better stick to an interface that prohibits usage of functions that have no defined meaning. Use ICollection<TicketFile>. This way you'll have only functions that have a proper meaning in the context of a database. Besides it gives entity framework the freedom to chose the most efficient collection type to execute its queries.
Let your classes represent the tables
Let your classes just be POCOs. Don't add any functionality that is not in your tables.
In entity framework the columns of a table are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
Let entity framework decide what's the most efficient to initialize the data in your sequences. Don't use a constructor where you create a List, which will be immediately thrown away by entity framework to replace it with its own ICollection. Don't automatically initialize property Deleted, if entity framework immediately replaces it with its own value.
You will probably have only one procedure where you will add a Ticket to the database. Use this function to properly initialize the field of any "newly added Ticket"
Don't forget the foreign keys
You defined several relations between your tables (one-to-many, or many-to-many?) but you forgot to define the foreign keys. Because of your use of virtual entity framework can understand that it needs foreign keys and will add them, but in your query you need to write e.Status != null && e.Status.Id == statusId, while obviously you could just use the foreign key e.StatusId == statusId. For this you don't have to join with the Statuses table
Another reason to specify the foreign keys: they are real columns in your tables. If you define that these classes represent your tables, they should be in these classes!
Only select the properties you actually plan to use
One of the slower parts of a database query is the transport of the selected data from the database management system to your local process. Hence it is wise to select only the data you actually plan to use.
Example. There seems to be a one-to-many between a User and a Ticket: every User has zero or more Tickets, every Ticket belongs to exactly one User. Suppose User 4 has 20 Tickets. Every Ticket will have a UserId with a value 4. If you fetch these 20 Tickets without a proper Select you will fetch all properties of the same User 4 once per Ticket, and you will transport the data of this same User 20 times (with all his properties, and maybe all his relations). What a waste of processing power!
Always use Select to query your data and Select only the properties you actually plan to use. Only use Include if you plan to updated the Included data.
var tickets = dbContext.Tickets.Where(ticket => !ticket.Deleted
// improvement: use foreign keys
&& ticket.ProjectId == 0 (or == null, if ProjectId nullable)
&& ticket.StatusId == statusId) // no Join with Statuses needed
.Select(ticket => new
{
...
}

How do I Get Asp.net web api to join 2 tables (1 to many relation)

I am new to Web Api and just trying to learn by playing with different examples. I am completely stuck on trying to write a Get request to return a complex type. I have 3 entities, 1 of the entities has a list of another entity, So I am trying to figure out how to return the data from within both.
I looked at some examples on stack overflow, that showed to use the .Include linq statement, but when I try that, I am getting compiler errors (type argument cannot be inferred.
Basically, I have a class of Players, Teams and Specialties. Once I get this working, I am planning on writing an angular project. The specialties table is a multiselect for a given player.
Here is what I have written so far
public class Player
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int JerseyNo { get; set; }
public DateTime DateAquired { get; set; }
public string Bio { get; set; }
[ForeignKey("TeamID")]
public virtual Team Team { get; set; }
public virtual IEnumerable<Specialty> Specialites { get; set; }
}
public class Specialty
{
public int Id { get; set; }
public string Speciality { get; set; }
public virtual Player Player { get; set; }
}
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public virtual Player Player { get; set; }
}
public class dbContext :DbContext
{
public DbSet<Player> Players { get; set; }
public DbSet<Team> Teams { get; set; }
public DbSet<Specialty> Specialties { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;");
}
}
When I created the database using migrations, it looks how I want it to, but cannot figure out Web Api's joins to get the data from my specialties table. The .Include cannot recognize any value I enter as parameters
private dbContext db = new dbContext();
// GET: api/values
[HttpGet]
public IEnumerable<Player> Get()
{
var teams = db.
Players
.Include("Specialties")
.Select(p=> new Player
Looks like this an Entity Framework question.
Try if you can get this to work, for debugging purpose:
var teams = db.Players.ToList();
foreach (var player in teams)
{
// Force lazy loading of Specialities property
player.Specialities.ToList();
}
If this doesn't work, it looks like EF cannot figure out the mapping to the database.

Querying data from a child table in .Net Mobile service

I have two simple models in .net backend based Azure Mobile Service Project, as shown below & I am not able to query the child table (querying parent table, UserItem, works just fine)
(The Id is nvarchar(128) & is autogenerated as newId by DB)
public class AnswerItem: EntityData
{
public string Content { get; set; }
public UserItem By { get; set; }
public QuestionItem ForQuestion { get; set; }
public double Rating { get; set; }
public string Comment { get; set; }
}
& a child to this UserItem Table as shown below
public class QuestionItem: EntityData
{
public string Content { get; set; }
public bool IsAnswered { get; set; }
public int NumberOfAnswers {get; set;}
public UserItem By { get; set; }
public string ById { get; set; }
public string AtLocation { get; set; }
}
As you notice, the QuestionItem has a FK relationship to UserItem table on ById field (Referencing Id field in UserItem Table)
The issue is I am getting a Bad Request error when I try to query the data from child table
Following are some queries that I tried
private IMobileServiceTable<QuestionItem> questionTable = App.MobileService.GetTable<QuestionItem>();
questions = await questionTable.Where(x=>x.IsAnswered==true).ToCollectionAsync(); (Does not Work)
questions = await questionTable.Where(x=>x.ById="UserIdGoesHere").ToCollectionAsync(); (Does Not Work)
questions = await questionTable.Where(x=>x.Content.StartsWith("q")).ToCollectionAsync(); (This Works)
questions = await questionTable.ToCollectionAsync(); (This Works as well)
If I fire a TSQL query in Sql Server Object explorer they all return correct values.
I am at my wits end on what could be wrong with my approach.
Any help is really appreciated.
Thanks
Supreet
Investigating further the Request it was generating was like this
192.168.2.4:50002/tables/QuestionItem?$filter=(byid eq 'myUniqueGuId')
analyzing fiddler output shows this error
"The query specified in the URI is not valid. Could not find a property named 'byid' on type 'x2Service.DataObjects.QuestionItem'"
Off course there is no fields in the table by the name of 'byid' the one I have is called 'ById' Its the JsonProperty adorner that changed it [JsonProperty(PropertyName = "byid")] In my client class.
Removed the Json Property & it worked just fine

Linq - How to to create a list of categories which are included in another table

I am trying to select from a list of categories where it matches the category type of a list of items using linq. IE, from a list of all the FIGstationeryCategories, select only the ones where the FiGStationeryType has a matching category from an already filtered list. The models are listed below.
public class FIGstationeryType
{
public int Id { get; set; }
public virtual FIGstationeryCategory Category { get; set; }
public virtual FIGcompany Company { get; set; }
public decimal Height { get; set; }
public decimal Width { get; set; }
public virtual FIGstationeryType Template { get; set; }
public bool DoubleSided { get; set; }
}
public class FIGstationeryCategory
{
public int Id { get; set; }
public string Name { get; set; }
public decimal MaxZoom { get; set; }
public ICollection<FIGstationeryType> StationeryItems { get; set; }
}
I have been going around in circles with this, and any help will be much appreciated. I haven't got very far! The first line of code works fine, it is the second one I am struggling with.
var listOfItems = db.StationeryTypes
.Where(C => C.Company.Users.Any(u => u.UserId == WebSecurity.CurrentUserId))
.ToList();
var categoryList = db.StationeryCategories
.Where(listOfItems
Any help would be much appreciated.
var listOfCategories =
(from o in listOfItems select o.Category.Name).Distinct().ToList();
When I thought about it (After watching 3 hours of linq videos last night), I realised that the listOfItems already held all the categories which where in use, so I didn't need to query and compare the two tables, just draw the relevant values from the list I already had.
I am not entirely sure how you want to select your categories but this probably goes a little way:
var categoryList = db.StationeryCategories
.*Select*(x => listOfItems.Where(y => y.Category == x)
.FirstOrDefault());
Can you clarify if this is the criteria you are after?

ASP.NET MVC4: How to pass a view model and other data to the view

I have created a ViewModel called DashboardViewModel:
public class DashboardViewModel
{
public Hardware Hardware { get; set; }
public Software Software { get; set; }
}
I am passing the ViewModel to the view in my ActionResult. But I need to pass other things too. Here is my ActionResult:
public ActionResult Index()
{
HardwareType hwt = new HardwareType { HType = "PC" };
IEnumerable<Hardware> Pcs = db.Hardware.Where(h => h.HardwareType.Contains(hwt));
DashboardViewModel dvm = new DashboardViewModel();
return View(dvm);
}
How do I pass Pcs to the view if I am already passing dvm? I don't even know if this is the right approach. What I am trying to accomplish is to create navigation on the page. So not only will I have PCs, but I'll have monitors and printers to pass to the view, as well as software. Here is my hardware class:
public class Hardware
{
public int Id { get; set; }
public virtual ICollection<DeviceType> Type { get; set; }
public string AssetTagId { get; set; }
public virtual ICollection<Manufacturer> Manufacturer { get; set; }
[Required]
[StringLength(50)]
public string ServiceTagId { get; set; }
[Required]
[StringLength(50)]
public string SerialNumber { get; set; }
[Required]
[StringLength(75)]
public string ProductNumber { get; set; }
// [Required]
[StringLength(20)]
public string PurchaseDate { get; set; }
[StringLength(20)]
public string WarrantyExpiration { get; set; }
[Required]
[StringLength(20)]
public string WarrantyType { get; set; }
public virtual ICollection<Location> Location { get; set; }
public virtual ICollection<HardwareType> HardwareType { get; set; }
[Required]
[StringLength(2000)]
public string Notes { get; set; }
public string POATag { get; set; }
}
What is the best approach for what I want to do (creating the navigation with various categories of hardware and software)? I'm new to MVC and am trying to follow suggestions on what to do, but I could use a higher level approach as maybe I'm going about this all wrong. Thanks.
You can put your Pcs in ViewBag or ViewData as below:
public ActionResult Index()
{
HardwareType hwt = new HardwareType { HType = "PC" };
IEnumerable<Hardware> Pcs = db.Hardware.Where(h => h.HardwareType.Contains(hwt));
ViewBag.Pcs=Pcs;//or ViewData["Pcs"]=Pcs;
DashboardViewModel dvm = new DashboardViewModel();
return View(dvm);
}
ViewBag is the dynamic object. You can add anything to it. With any name e.g. yous Pcs can also be stored in ViewBag as ViewBag.AnyNameYouLike=Pcs;
**RAZOR SYNTAX:**
Just apply loop and you are done.
#foreach(var pc in ViewBag.Pcs)
{
#pc.Id;//Will give you id
}
You can loop through all properties like this
Create a top level view-model - like you have DashboardViewModel - and add all the necessary models as Properties.
It would be good if you created view-models for each business model required in that top level view-model.
Auto-map the business objects to the new view-models - see AutoMapper for one example. That way you are only passing the information the view actually requires.