I recently finished an MVC course on udemy that built a video rental app. He covered how to check out a movie but left on our own to figure out how check it back in.
I have a Customer model:
public class Customer
{
public int Id { get; set; }
[Required(ErrorMessage = "Please enter customer's name.")]
[StringLength(255)]
public string Name { get; set; }
public bool IsSubscribedToNewsletter { get; set; }
[Display(Name = "Date of Birth")]
[Min18YearsIfAMember]
public DateTime? Birthdate { get; set; }
public MembershipType MembershipType { get; set; }
[Display(Name = "Membership Type")]
public byte MembershipTypeId { get; set; }
}
A Movie model:
public class Movie
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public Genre Genre { get; set; }
[Display(Name = "Genre")]
[Required]
public byte GenreId { get; set; }
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; set; }
public DateTime DateAdded { get; set; }
[Display(Name = "Number in Stock")]
[Range(1, 20)]
public byte NumberInStock { get; set; }
public byte NumberAvailable { get; set; }
}
And a Rental model that contains a customer and a movie:
public class Rental
{
public int Id { get; set; }
[Required]
public Customer Customer { get; set; }
[Display(Name = "Customer Name")]
public int CustomerId { get; set; }
[Required]
public Movie Movie { get; set; }
public DateTime DateRented { get; set; }
public DateTime? DateReturned { get; set; }
}
I made a view that will display the active rentals and I made a Delete action that will delete it as if the movie has been turned in, but I can't figure out how to put that movie back in stock by increasing the number of available movies(Movies.NumberAvailable). I tried to do it in the same action as the Delete action but I'm having no luck. Here's the Delete action:
public ActionResult Delete(int id)
{
Rental rental = _context.Rentals.Find(id);
//var movie = _context.Movies.Where(m => rental.Movie.Id.Contains(m.Id));
rental.Movie.NumberAvailable++;
_context.Rentals.Remove(rental);
_context.SaveChanges();
return RedirectToAction("List");
}
I tried to pull out just that movie into it's own variable and add 1 to that but the rental.Movie.Id part popped an error saying it doesn't have a definition for Contain. If I run it as it is above, I get an exception at rental.Movie.NumberAvailable++; saying The 'Object reference not set to an instance of an object.'
Does anyone have any ideas on I can do to fix this? Yes, I'm a noob.
try :
Rental rental = _context.Rentals.Where(r => r.Id == id).FirstOrDefault();
rental.Movie.NumberAvailable++;
There are a couple approaches I can suggest to tackle problems like this.
Track the movie instances and their status and calculate the totals on demand.
This would be something like a Movie class, but then each movie would have a collection of MovieDisc for instance:
public class MovieDisc
{
public int MovieDiscId{ get; set; }
public bool InStock { get; set; }
public string Barcode { get; set; }
}
Basically as movies are checked out or scanned in, their barcode identifies the "disc" or instance and sets the InStock. From the movie side:
public class Movie
{
// ...
public virtual ICollection<MovieDisc> Discs {get; internal set;} = new List<MovieDisc>();
public byte NumberInStock
{
get { return Discs.Count(x => x.InStock); }
}
public byte NumberAvailable
{
get { return Discs.Count(x => !x.InStock); }
}
}
The caveat of this approach is that to use these properties, the Discs collection needs to be eager loaded, or will trip a lazy load.
Adopt a DDD approach to the entities.
Domain Driven Design essentially adds controls to how state changes in the domain. Rather than using individual setters for values, you use methods or actions on the domain entity to validate and control the valid, and complete changes allowed against the domain.
public class Movie
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; private set; }
public Genre Genre { get; private set; }
[Display(Name = "Genre")]
[Required]
public byte GenreId { get; private set; }
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; private set; }
public DateTime DateAdded { get; private set; }
[Display(Name = "Number in Stock")]
[Range(1, 20)]
public byte NumberInStock { get; private set; }
public byte NumberAvailable { get; private set; }
public void RentOneOut()
{
if (NumberInStock <= 0)
throw new InvalidOperation("Cannot rent out a movie that has no stock.");
if (NumberAvailable <= 0)
throw new InvalidOperation("All movie copies are out.");
NumberAvailable -= 1;
}
public void ReturnOneIn()
{
if (NumberInStock <= 0)
throw new InvalidOperation("Cannot return a movie that has no stock.");
if (NumberAvailable >= NumberInStock)
throw new InvalidOperation("All movie copies are already in. Stocktake needed.");
NumberAvailable += 1;
}
}
Note all setters are private. (or internal if you want to enable unit testing) The objective is to express valid operations against a domain entity as a method. This ensures that multiple checks and updates are performed as a whole so that multiple properties can be updated together rather than risking leaving an entity in an incomplete state.
This might be more practical for your rental scenario:
public function ReturnRental(Rental rental)
{
if (rental == null)
throw new ArgumentNullException("rental");
rental.Return();
}
// In Rental:
public class Rental
{
// ... private setters, like in Movie.
public void Return()
{
Movie.ReturnOneIn();
DateReturned = DateTime.Today;
}
}
You'd want to handle the scenario where the return failed for whatever reason. (data state out of sync)
Hopefully that gives you a few ideas.
Related
I have a model class and I want to make on the parameter "required" only if the value of another parameter is something.
[JsonConverter(typeof(JsonStringEnumConverter))]
[Required]
public AddressTagEnum AddressTagId { get; set; }
[RequiredIf("AddressTagId", 3)]
[MaxLength(20)]
public string AddressTagOther { get; set; }
How can I achieve this?
Thanks in advance.
there are several ways to solve your problem:
there is no standard way to do this
there are librariers e.g. Expressive Annotations that should help
there is a built-in attribute Remote that lets you perform a server validation like: [Remote(action: "VerifyEmail", controller: "Users")] see docs
i prefer (i know that this is opinion based) a implementation with IValidatableObject. Citing the docs:
public class ValidatableMovie : IValidatableObject
{
private const int _classicYear = 1960;
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; set; }
[Required]
[StringLength(1000)]
public string Description { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
public Genre Genre { get; set; }
public bool Preorder { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
{
yield return new ValidationResult(
$"Classic movies must have a release year no later than {_classicYear}.",
new[] { nameof(ReleaseDate) });
}
}
}
Currently I am working on changing the values within a table row, which includes the following variables:
public int ID { get; set; }
public string Item_Name { get; set; }
[DisplayFormat(DataFormatString = "{0:0.##}")]
public decimal Price { get; set; }
public int TimeSlot { get; set; }
public bool Food_AddOns { get; set; }
public bool Drink_AddOns { get; set; }
public virtual Item_Description Item_Description { get; set; }
public virtual Item_Status Item_Status { get; set; }
public virtual Dinner Dinner { get; set; }
public string Ingredients { get; set; }
My View passes the values that are given from the user to this model:
public class Edit_AddItemModel
{
[Display(Name = "ID")]
public int ID { get; set; }
[Display(Name = "Item Name:")]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 1)]
public string New_ItemName { get; set; }
[DisplayFormat(DataFormatString = "{0:0.##}", ApplyFormatInEditMode = true)]
[Display(Name = "Price:")]
public decimal New_Price { get; set; }
[Display(Name = "Time Slot:")]
public int New_TimeSlot { get; set; }
[Display(Name = "Lunch Special?:")]
public bool New_Food_AddOns { get; set; }
[Display(Name = "Free Drink?:")]
public bool Drink_AddOns { get; set; }
[Display(Name = "Item Description:")]
public string New_Item_Description { get; set; }
public bool New_spicy { get; set; }
public bool New_gluten { get; set; }
public bool New_vegetarian { get; set; }
[Display(Name = "Dinner:")]
public string New_Dinner { get; set; }
[Display(Name = "Ingredients:")]
[StringLength(140, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 0)]
public string New_Ingredients { get; set; }
}
My values are passed correctly to the controller as illustrated in the image below, and is passed to a function called Edit_CheckAllValues. This is what the Edit_CheckAllValues looks like, which basically creates an ItemsModel Object with the what has been passed from the View:
private ItemsModel Edit_CheckAllValues(ItemsModel NewItem, Edit_AddItemModel model)
{
int CurrentItem_ItemStatus;
NewItem.ID = model.ID;
NewItem.Item_Name = model.New_ItemName;
NewItem.Price = model.New_Price;
NewItem.TimeSlot = model.New_TimeSlot;
NewItem.Food_AddOns = model.New_Food_AddOns;
NewItem.Drink_AddOns = model.Drink_AddOns;
NewItem.Item_Description = convertToForeignKey_ItemDescription(Convert.ToInt32(model.New_Item_Description));
//Get Current Item Status.
CurrentItem_ItemStatus = get_ItemStatus(model.New_spicy, model.New_gluten, model.New_vegetarian);
NewItem.Item_Status = convertToForeignKey_ItemStatus(CurrentItem_ItemStatus);
NewItem.Dinner = convertToForeignKey_Dinner(Convert.ToInt32(model.New_Dinner));
NewItem.Ingredients = model.New_Ingredients;
return NewItem;
}
An Example of what is returned, what the new ItemModel looks like, which contains the ID of the item I want to edit: The Return Value:
An Example of one of the foreign keys being changed from ID: 20 to ID: 2 Item Description Foreign Key Change:
The New Object is then passed back to the original ActionResult EditItem, which it is changing the state of the item selected, which works correctly when changing variables that are not foreign keys such as price, Item Name, TimeSlot, etc:
public ActionResult EditItem(EditItemModel model)
{
if (ModelState.IsValid)
{
ItemsModel newItem = new ItemsModel();
newItem = Edit_CheckAllValues(newItem, model.edit_AddItemModel);
ApplicationDbContext db = new ApplicationDbContext();
db.Items.Attach(newItem); //Tired both with Attach and Without Attach
db.Entry(newItem).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("ChangeItems", "Employee");
}
I'm not sure where in my code I need to make edits, but my guess is I need to grab the row first within my Edit_CheckAllValues instead of passing the values into a new Object and sending it to the database using EntityState.Modified.
Any help would be appreciated, for I have been stuck on this question for 3 days now.
Based off the reference to this page (ASP.Net MVC 4 Update value linked to a foreign key column)
I was able to figure out the problem. The portions I needed to update were my ItemsModel:
public class ItemsModel
{
public int ID { get; set; }
public string Item_Name { get; set; }
[DisplayFormat(DataFormatString = "{0:0.##}")]
public decimal Price { get; set; }
public int TimeSlot { get; set; }
public bool Food_AddOns { get; set; }
public bool Drink_AddOns { get; set; }
[ForeignKey("Item_Description")]
public virtual int Item_Description_ID { get; set; }
public virtual Item_Description Item_Description { get; set; }
[ForeignKey("Item_Status")]
public virtual int Item_Status_ID { get; set; }
public virtual Item_Status Item_Status { get; set; }
[ForeignKey("Dinner")]
public virtual int Dinner_ID { get; set; }
public virtual Dinner Dinner { get; set; }
public string Ingredients { get; set; }
}
Adding the [ForeignKey] from the API: using System.ComponentModel.DataAnnotations.Schema;
and update my Edit_CheckAllValues function:
private ItemsModel Edit_CheckAllValues(ItemsModel NewItem, Edit_AddItemModel model)
{
int CurrentItem_ItemStatus;
NewItem.ID = model.ID;
NewItem.Item_Name = model.New_ItemName;
NewItem.Price = model.New_Price;
NewItem.TimeSlot = model.New_TimeSlot;
NewItem.Food_AddOns = model.New_Food_AddOns;
NewItem.Drink_AddOns = model.Drink_AddOns;
//NewItem.Item_Description = convertToForeignKey_ItemDescription(Convert.ToInt32(model.New_Item_Description));
NewItem.Item_Description_ID = Convert.ToInt32(model.New_Item_Description);
//Get Current Item Status.
CurrentItem_ItemStatus = get_ItemStatus(model.New_spicy, model.New_gluten, model.New_vegetarian);
NewItem.Item_Status_ID = CurrentItem_ItemStatus;
// NewItem.Item_Status = convertToForeignKey_ItemStatus(CurrentItem_ItemStatus);
NewItem.Dinner_ID = 163; //Ignore this is my empty default Dinner.
// NewItem.Dinner = convertToForeignKey_Dinner(Convert.ToInt32(model.New_Dinner));
// CurrentItem.Item_Description = convertToForeignKey_ItemDescription(Convert.ToInt32(model.New_Item_Description));
NewItem.Ingredients = model.New_Ingredients;
return NewItem;
}
The entity-modified will finally register the changes to the foreign keys.
I'm about to create an ASP.Net Web Application with MVC and EF for the following scenario:
Every month there are new people entering a department in our company. Until now we're using an Excel Spreadsheet to handle common "Workitems" such as granting filesystem permissions etc.
Now I want to handle those requests using a Webapplication but I'm stuck at creating the Model.
There are two different Requesttypes, "JoinRequest" and "ChangeRequest" for people joining the company and employees changing the department. For each request type there are different workitems defined. Those workitems should be displayed in the webapp when the corresponding request is selected. After the data is loaded the user has to enter the data for the workitems.
E.g.
[JoinRequest]
Add new Filesystem Access permission:
[ UNC Path ] [x] Read [ ] Read & Write
Here the user has to enter the UNC Path and the type of Accesslevel (R or RW).
For now I've got the following Model:
public abstract class DbItem {
[Key]
public int Id { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? DeletionDate { get; set; }
public string CreatedBy { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted => DeletionDate != null;
}
public abstract class Request : DbItem {
public Department NewDepartment { get; set; }
public DateTime BeginDate { get; set; }
public DateTime? EndDate { get; set; }
public ContractType ContractType { get; set; }
public EmployeeType EmployeeType { get; set; }
public string Location { get; set; }
public string ContactUserId { get; set; }
[NotMapped]
public User Contact {
get {
if (!string.IsNullOrWhiteSpace(ContactUserId)) {
var userSearcher = new ActiveDirectoryUserSearcher();
return userSearcher.FindByUserID(ContactUserId);
}
return null;
}
}
public string Phonenumber { get; set; }
//public virtual List<RequestWorkItem> RequestWorkItems { get; set; }
public bool IsFinished { get; set; }
public abstract string GetRequestType();
}
public abstract class WorkItem : DbItem {
[Key]
[ForeignKey("Language")]
public int LanguageId { get; set; }
[NotMapped]
public virtual Language Language { get; set; }
public string WorkItemText { get; set; }
public virtual Category Category { get; set; }
}
public class JoinRequest : Request {
public Person Person { get; set; }
public override string GetRequestType() {
return "New";
}
}
public class FileSystemAccessWorkItem : AccessWorkItem {
public FileSystemAccessRight FileSystemAccessRight { get; set; }
public string FileSystemPath { get; set; }
}
So when a user visits the website a new request has to be created but for every request type a set of workitems has to be loaded from the database. Additionally for every workitem the data the user entered should be saved somehow.
I hope you understand what I'm looking for - If anything is unclear I'll do my best to explain it more precisely
Edit1:
When looking at my Models I think the only think I achived is defining my Requests (so e.g. a JoinRequest contains some specific WorkItems) but how can I achive that a user is able to use this "RequestTemplate" to create a new Request and fill in the data to the corresponding workitems. Do I need additional Models for that? Maybe an example?
Is there any way to somehow combine the data from two models and THEN map them both to the same viewModel in the context of an edit action?
I have never had to update several tables at once in an edit action in ASP.NET MVC with Entity Framework 6.1.3. This is the layout:
I have a DB table called "Address" which has fields for StreetNumber, StreetName, City, State, ZipCode. It has a one-to-one relationship with another table called Bars. As in, a bar can only have one address and one address can only have one bar.
Because I am storing this data in two separate tables, I am having a very difficult time trying to successfully implement an Edit action which takes data from one form (BarForm) and should update both the Bar and Address database tables. See my code:
BarController
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(m => m.Id == id);
var address = _context.Addresses.SingleOrDefault(a => a.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar, new BarFormViewModel());
if (address == null)
{
address = new Address();
}
Mapper.Map<Address, BarFormViewModel>(address, viewModel);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(BarFormViewModel bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<BarFormViewModel, BarFormViewModel>(bar, new BarFormViewModel());
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.Id == 0)
{
var newbar = Mapper.Map<BarFormViewModel, Bar>(bar);
newbar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(newbar);
var addressToAdd = Mapper.Map<BarFormViewModel, Address>(bar);
_context.Addresses.Add(addressToAdd);
}
else
{
var barInDb = _context.Bars.Single(b => b.Id == bar.Id);
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Mapper.Map<BarFormViewModel, Bar>(bar, barInDb);
Mapper.Map<BarFormViewModel, Address>(bar, addressInDb);
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
Domain Models:
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
}
public class Address
{
public int Id { get; set; }
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public Bar Bar { get; set; }
public int BarId { get; set; }
}
View Model which includes both Address and Bar properties:
{
public class BarFormViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
[Display(Name = "Google Place ID")]
public string GooglePlaceId { get; set; }
[Display(Name = "Sunday Happy Hour Info:")]
public string SundayDiscounts { get; set; }
[Display(Name = "Monday Happy Hour Info:")]
public string MondayDiscounts { get; set; }
[Display(Name = "Tuesday Happy Hour Info:")]
public string TuesdayDiscounts { get; set; }
[Display(Name = "Wednesday Happy Hour Info:")]
public string WednesdayDiscounts { get; set; }
[Display(Name = "Thursday Happy Hour Info:")]
public string ThursdayDiscounts { get; set; }
[Display(Name = "Friday Happy Hour Info:")]
public string FridayDiscounts { get; set; }
[Display(Name = "Saturday Happy Hour Info:")]
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime? LastUpdated { get; set; }
//Address Model Info
public Address Address { get; set; }
public int? AddressId { get; set; }
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int? ZipCode { get; set; }
public bool IsNew { get; set; }
}
The problem here is that I am getting an empty AddressId with this setup, which is causing an exception when the Save action gets run. This is because the BarForm view is getting passed a ViewModel which has been mapped from a Bar object and the Bar domain model actually has no Address information in it, since it is not the Address model/table.
Is there any way to somehow combine the data from both the Address and Bar models and THEN map them both to the same viewModel?
I keep getting a Sequence Contains no Elements error for this line in the Save action:
var addressInDb = _context.Addresses.Single(a => a.Id == bar.AddressId);
I also tried:
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Neither work. I understand what the error is saying and have also checked the actual HTML for my hidden Addressid field and it is blank... See code in my BarForm View:
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.AddressId)
#Html.AntiForgeryToken()
Remove the new BarFormViewModel() as the second parameter in your mapping calls as it is not necessary.
In your post action, inside your if statement that checks if the ModelState is valid and if bar.Id == 0, bar is already a view model, so no need to mapping.
And when you create your AutoMapper mapping, you must create a custom property mapping because the Address.Id property will not map automatically to the AddressId property as the name is not the same.
AutoMapper.Mapper.CreateMap<Address, BarFormViewModel>()
.ForMember(dest => dest.AddressId, o => o.MapFrom(source => source.Id));
And then do the same for the inverse mapping.
When calling a view which displays properties for ASP.NET's ApplicationUser class which I have extended, I receive the below error when the code tries to render a line where I dive into a ApplicationUser class:
#model BaseballStatTracker.Models.ApplicationUser
#foreach (var stats in Model.GameStatistics)
{
<tr>
<td>
#Html.DisplayFor(modelItem => stat.Game.GameTime)
</td>
</tr>
}
...causes:
Any property accessed from within the "Game" property causes the error, and Razor gives no indication that there is a problem when editing the view. Just accessing regular properties on the GameStatistics works without issue.
I have two contexts; the standard ApplicationDbContext and my GamesContext which houses both the Game and GameStatistics entities. I have overridden the OnModelCreating method on the GamesContext per the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>().HasKey(i => i.GameId);
modelBuilder.Entity<Game>().HasOptional(i => i.HomeTeam).WithMany(i => i.HomeGames).HasForeignKey(i => i.HomeTeamId);
modelBuilder.Entity<Game>().HasOptional(i => i.AwayTeam).WithMany(i => i.AwayGames).HasForeignKey(i => i.AwayTeamId);
modelBuilder.Entity<Game>().HasOptional(i => i.Diamond).WithMany(i => i.Games).HasForeignKey(i => i.DiamondId);
modelBuilder.Entity<GameStatistics>().HasKey(i => i.GameStatisticsId);
modelBuilder.Entity<GameStatistics>().HasRequired(i => i.Game).WithMany(i => i.GameStatistics).HasForeignKey(i => i.GameId);
modelBuilder.Entity<GameStatistics>().HasRequired(i => i.Player).WithMany(i => i.GameStatistics).HasForeignKey(i => i.PlayerId);
...
}
The ApplicationUser, Game and GameStatistics classes look like the following:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[Required]
[Display(Name = "First Name")]
[StringLength(155, ErrorMessage = "First Name can only be max 155 characters in length.")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(155, ErrorMessage = "Last Name can only be max 155 characters in length.")]
public string LastName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return FirstName + " " + LastName;
}
}
[Display(Name = "Player Number")]
public int PlayerNumber { get; set; }
[Display(Name = "Team")]
public virtual Guid TeamId { get; set; }
public virtual Team Team { get; set; }
public virtual ICollection<GameStatistics> GameStatistics { get; set; }
}
Game:
public class Game
{
[Key]
public Guid GameId { get; set; }
public string GameName { get
{
return AwayTeam.Name + " # " + HomeTeam.Name + ", " + GameDate.Value.Date.ToLongDateString();
} }
[DataType(DataType.Date)]
[Display(Name = "Game Date")]
public DateTime? GameDate { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Game Time")]
public DateTime? GameTime { get; set; }
[Display(Name = "Game Location")]
public virtual Guid? DiamondId { get; set; }
public virtual Diamond Diamond { get; set; }
[Display(Name = "Home Team")]
public virtual Guid? HomeTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
[Display(Name = "Away Team")]
public virtual Guid? AwayTeamId { get; set; }
public virtual Team AwayTeam { get; set; }
public virtual ICollection<GameStatistics> GameStatistics { get; set; }
}
GameStatistics:
public class GameStatistics
{
[Key]
public Guid GameStatisticsId { get; set; }
public Guid GameId { get; set; }
public virtual Game Game { get; set; }
public string PlayerId { get; set; }
public virtual ApplicationUser Player { get; set; }
public int AtBats { get; set; }
public int Hits { get; set; }
public int Walks { get; set; }
}
I have tried redoing the OnModelCreate method but I can't see what I have done wrong with the Game<-GameStatistics relationship. Any help would be much appreciated. Thank you in advance -
This issue was resolved after adding a couple of "Inverse Property" annotations to my Games model on the HomeTeam and AwayTeam properties. I also moved away from using the OnModelCreating override and switched to using the annotations completely. The relevant part of the change on the code was:
[Display(Name = "Home Team")]
public virtual Guid? HomeTeamId { get; set; }
[ForeignKey("HomeTeamId")]
[InverseProperty("HomeGames")] // Added this -
public virtual Team HomeTeam { get; set; }
[Display(Name = "Away Team")]
public virtual Guid? AwayTeamId { get; set; }
[ForeignKey("AwayTeamId")]
[InverseProperty("AwayGames")] // Added this -
public virtual Team AwayTeam { get; set; }
I am not sure why I was not able to achieve the same result setting the configuration in the data contexts - I am sure it was just a misunderstanding on my part but I would love to know why. Should EF not have known via the configuration what the inverse properties were?
I had tried changing the configuration setup but to no effect - I could not see anything wrong with it in the first place, however. Nevertheless, it is now working using the data annotations.