MVC / EF Code First ICollection Objects - asp.net-mvc

I am using MVC5 Code First and have a couple of classes that look like;
public class Asset
{
public int Id { get; set; }
public string FileName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public int AssetId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
I would like to return a view that lists all Assets that have a particular Category. I was hoping for something along the lines of
public ActionResult ListByCategory(string categoryName)
{
var model =
from r in _db.Assets
.Where(r => r.Categories.CategoryName == categoryName)
select r;
return View(model);
}
I know I have some assets from my seed method that have categories that exist. But the compiler is saying "System.Collection.Generic.ICollection Does not contain a definition for CategoryName and no extension method could be found, am I missing a reference?" This is on my .Where line.
I don't fully understand what it's trying to tell me. I do have a reference to my Models as I can reference them elsewhere within the controller. I know that a single Asset might be in several Categories, hence I created the ICollection at the class level.
Here is my context class;
public class AssetsDb : DbContext
{
public AssetsDb() : base("DefaultConnection")
{
}
public DbSet<Asset> Assets { get; set; }
public DbSet<Category> Categories { get; set; }
}
Could somebody help my understanding on how I can get to my underlying data? I'm trying to learn EF / MVC so appreciate any help.
Thanks.

You cannot get a CategoryName from a collection of categories, you need to check the name of each category within the collection.
Try using this query instead:
var model =
from r in _db.Assets
.Where(r => r.Categories.Any(c => c.CategoryName == categoryName))
select r;

Related

MVC View Page not showing Value

My Model class is below:
I have three classes two of them are partial class and one is simple class.......
[MetadataType(typeof(RegistrationMetaData))]
public partial class Registration
{
}
public class RegistrationMetaData
{
public string Id { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public virtual ICollection<Images> Images { get; set; }
}
[MetadataType(typeof(ImagesMetaData))]
public partial class Images
{
}
public class ImagesMetaData
{
public string id { get; set; }
public string stuid { get; set; }
public string stuimg1 { get; set; }
public virtual ICollection<Registration> Registrations { get; set; }
}
public class EmployeeViewModel
{
public string Name { get; set; }
public string stuimg1 { get; set; }
}
public class NotificationViewModel
{
public Registration registration { get; set; }
public Notification notification { get; set; }
public IEnumerable<EmployeeViewModel> EmployeeViewModel { get; set; }
public Images images { get; set; }
}
Then I have used the below code in controller class to fetch Images record with foreign key in registration table.
IEnumerable<EmployeeViewModel> model1 = null;
model1 = (from e in db.Registrations
join j in db.Images on e.Id equals j.stuid
where e.Email == Email
select new EmployeeViewModel
{
Name = e.Name,
stuimg1 = j.stuimg1
}).ToList();
var mixmodel = new NotificationViewModel
{
EmployeeViewModel = model1
};
return View(mixmodel);
Atlast my view page is like this:-
#model IEnumerable<Amar.Models.NotificationViewModel>
#foreach (var item in Model)
{
#item.EmployeeViewModel.stuimg1
}
But I am getting an error
System.Collections.Generic.IEnumerable' does not contain a definition for 'stuimg1' and no extension method 'stuimg1' accepting a first argument of type 'System.Collections.Generic.IEnumerable' could be found (are you missing a using directive or an assembly reference?)
I have gone through debugging my code is fine till controller page...but from view page the values are not showing.I think there is some little mistake i have made...
Please someone help me..I am trying to solve this problem since 2 weeks....
I want to fetch data from one more class on the same view page thats why I am using NotificationViewModel class.
return View(mixmodel);
Your view is expecting a type of IEnumerable<NotificationViewModel> but mixmodel is just a NotificationViewModel
I'm not seeing what you are achieving by having this additional class 'NotificationViewModel'. If your view were to be changed to expect an IEnumerable<EmployeeViewModal> you could pass in model1 directly.
You could then change your view to:
#foreach (var item in Model)
{
#item.stuimg1
}
If you do indeed need this extra layer of abstraction for some other part of your view then you need to sit and think about what is a a single instance and what is a list. Try drawing it if you're having trouble.

How to pass two LINQ joined tables from Controller to View

I have three objects:
public class Part
{
[Key]
public int ID { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public int descID { get; set; }
}
public class Description
{
[Key]
public int descID { get; set; }
public string descName { get; set; }
public string description { get; set; }
}
public class GridPart
{
public string name{ get; set; }
public string number { get; set; }
public string description { get; set; }
}
I'm using LINQ to join Part and Description on the descID column:
public ActionResult Index()
{
var myParts = from p in db.Parts
join d in db.Description on p.descID equals d.DescriptorID
select new { Description = d.description, Name = p.name};
List<GridPart> partsList = new List<GridPart>();
foreach (var m in myParts)
{
GridPart gp = new GridPart();
gp.Name = m.name;
gp.description = m.description;
partsList.Add(gp);
}
return View(partsList);
}
If I was just using the Parts table, in the view I would do:
#model IEnumerable<MyApp.Models.Part>
What do I do if I'm using the joined table? This uses both Parts and Description, not to mention my List of GridParts, how do I pass this through to display all of the data I need?
Any advice is greatly appreciated.
If you pass an anonymous type to your view it won't be strongly typed. You can refer to your model like this instead
#Html.TextBox("Name")
or
#Html.Display("Name")
Although this will work I would advice against it - The better solution would be to go with a Viewmodel instead. This will make your view strongly typed.
Edit: Looking on this again I see that your actually not parsing the anonymous type to your view. Your parsing a list of GridParts.
You should be able to strongly type your view like your tried - just refer to GridParts instead of parts.
#model IEnumerable<MyApp.Models.GridPart>

Saving many to many relationship tables in Asp.Net MVC

I use Asp.Net MVC, Entity Framework. I have a form it looks like below.
Here, dropdownlist is filled from a table(types). Checkboxes is filled from another table(test). Tables are like below:
public class Types
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
}
public class Tests
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
}
public class Types_Tests
{
public int Types_TestsID{ get; set; }
public int TypesID { get; set; }
public int TestsID { get; set; }
public virtual Types Types { get; set; }
public virtual Tests Tests { get; set; }
}
Types_test table is relation table between Types and Tests. When I click Kaydet button, it shuld save type and checked tests. I made this operation using ViewBag, javascript and hdnvalue.I added checked checkboz values to a hdntext. I made saving process like below:
[HttpPost]
public ActionResult Index(string drpType, string hdntesttypes)
{
var TypeList = Types.GetAll();
ViewBag.TypesList = new SelectList(TypeList, "Id", "Name");
var testypeList = testTypes.GetAll();
ViewBag.TestTypesList = new SelectList(testypeList, "Id", "TestName");
GenericRepository<TestDisabledTypes> testDisabledRepository = new GenericRepository<TestDisabledTypes>(_context);
if (!string.IsNullOrEmpty(hdntesttypes))
{
string[] disabletypesArray = hdntesttypes.Split(',');
using (TransactionScope trns = new TransactionScope())
{
for (int i = 0; i < disabletypesArray.Length; i++)
{
Test_Types types = new Test_Types ();
types.TestTypesID = Convert.ToInt32(disabletypesArray[i]);
types.TypesID = Convert.ToInt32(drpType);
testDisabledRepository.Insert(types);
}
trns.Complete();
}
}
return View();
}
It wokrs. But I search better solution for this process. Can someone give me any idea?
Thanks.
If you don't need additional attributes for your entity class, you don't need create link table.
Just define the following class, and EF will generate the link table for you automatically.
public class Type
{
public int TypesID{get;set;}
public string TestName { get; set; }
public string TestExplanation { get; set; }
public int TestTime { get; set; }
public ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestID{get;set;
public string Name { get; set; }
public string Code { get; set; }
public ICollection<Type> Types {get;set;}
}
Well, in EntityFramework if you want to create a many to many relation object you need to create new object of "linking" entity. Unfortunately, it is not possible to add first object, add second object and say "Guys, you are in many to many relationships. Are you happy then?" :) You need to create relation object, set appropriate fields in it (I think these are ids of two objects itself) and add it to relation collection (entity) in your model. But before doing so you need to be sure that objects with data you are linking with are already exists in database. Otherwise you'll get an error
Also it's not necessary to create manually transaction because EF does it for you automatically each time you get/save your data

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

MVC model duplication

I'm very new to ASP.NET MVC, so forgive me if this is something I should know. I haven't seen any obvious documentation on it, so here goes:
I have a LINQ to Entities data model and a MVC project. I use a lot of javascript/jquery, so have opted to access my data from the client through a WebAPI as json objects. However, I don't want to pass all the entity object properties though to the client, so I have added separate models to my MVC project in which I handle MVC model validation and Binding to my Views. Also, in order to work with it in my jquery, I have created json versions of the models.
This is only the start of the project and I don't want to start it off on the wrong foot. Having three versions of my models for each entity in my business layer is going to be a nightmare! I am sure that the overall structure of my project is a very common one, but can't see many developers settling for such duplication of code. There must be a better way of implementing it.
Any thoughts? Really appreciate any input.
In answer to your comment above - you can create your javascript viewmodel as a standard js object. I tend to use Knockout.js so it would look like this:
jsController.Resource = function (data) {
self.UserId = ko.observable(data.UserId);
self.FullName = ko.observable(data.Name);
self.RoleName = ko.observable(data.RoleName);
self.RoleId = ko.observable(data.RoleId);
}
and then use an ajax post method to post it to your MVC action
jsController.addToUndertaking = function (resource, isAsync) {
mylog.log("UndertakingId at post = " + jsController.undertakingId);
var action = $.ajax({
type: "POST",
url: "/TeamMember/AddUserToUndertaking",
data: resource,
cache: false,
async: isAsync
});
action.done(function () {
resource.AllocatedToUndertaking(true);
//Do other funky stuff
});
};
Create your MVC action so that it accepts a forms collection as so:
public ActionResult AddUserToUndertaking(FormCollection postedResource)
{
if (Request.IsAjaxRequest() == false)
{
const string msg = "Non ajax request received";
Logger.ErrorFormat(msg);
throw new SecurityException(msg);
}
if (postedResource == null)
{
Logger.Debug("Null resource posted - terminating.");
return new HttpStatusCodeResult(500);
}
var resource = new AllocatedResourceAjaxViewModel(postedResource);
//Do something Funky
return new HttpStatusCodeResult(200);
}
and then you create your MVC viewmodel from the forms collection (i tend to do this by passing in the forms collection as a constructor method to the viewmodel).
public class AllocatedResourceAjaxViewModel
{
public int UserId { get; set; }
public string Name { get; set; }
public string RoleName { get; set; }
public int RoleId { get; set; }
public AllocatedResourceAjaxViewModel()
{}
public AllocatedResourceAjaxViewModel(NameValueCollection formData)
{
UserId = JsFormDataConverter.Int(formData["UserId"]);
Name = Convert.ToString(formData["FullName"]);
RoleName = Convert.ToString(formData["RoleName"]);
RoleId = JsFormDataConverter.Int(formData["RoleId"]);
}
}
As a null int in your javascript VM will lead to a string of 'undefined' being passed you need a converter method to safely extract non strings.
public static class JsFormDataConverter
{
public static bool Boolean(string formValue, bool defaultValue = false)
{
if (formValue.ToLower() == "true") return true;
if (formValue.ToLower() == "false") return false;
return defaultValue;
}
public static int Int(string formValue, int defaultValue = 0)
{
int result;
return int.TryParse(formValue, out result)
? result
: defaultValue;
}
}
and there you go. I am sure you can improve on the above but that will get you going.
The way that I have always worked is that you have your Models e.g. Order & OrderLines which are where you store all your data and get hydrated either directly from the database by SQL or (more usually these days ) by an ORM such as NHibernate or Entity Framework.
You then have ViewModels - these are used to transport the data from your application to the views - either directly ie a strongly typed view bound to say an OrderViewModel or via an action returning a JsonResult.
A OrderViewModel is not a duplication of Order as it is designed to only hold the data that is needed to be presented on the screen (If you have many different views displaying an Order in different ways it could be perfectly acceptable to have many different ViewModels -one for each view containing only the fields needed for each view). ViewModels should also not contain any complex types except other ViewModels. this helps keep accidental data access out of the views (think security and performance).
So Given
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public User User { get; set; }
public string Description { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
public class OrderLine
{
public int Id { get; set; }
public Order Order { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public int Weight { get; set; }
public int Price { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
You could have the two ViewModels
public class OrderViewModel
{
public int ID { get; set; }
public List<OrderLineViewModel> OrderLines { get; set; }
public DateTime OrderDate { get; set; }
public int UserId { get; set; }
public string Description { get; set; }
}
public class OrderLineViewModel
{
public int Id { get; set; }
public int OrderId { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public int Weight { get; set; }
public int Price { get; set; }
}
The view models could then be serialized into JSON as needed or marked up with validation attributes etc.

Resources