Why does my class not implement its properties? - c#

I have a class named Restaurant:
namespace Futures.Api.Data.Models
{
public class Restaurant
{
public Restaurant()
{
Dishes = new HashSet<Dish>();
}
[Display(Name = "Restaurant Id")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RestaurantId { get; set; }
[Display(Name = "Restaurant Name")]
public string RestaurantName { get; set; } = null!;
[Display(Name = "Category Name")]
public string CategoryName { get; set; } = null!;
[Display(Name = "Restaurant Address")]
public string RestaurantAddress { get; set; } = null!;
[Display(Name = "Zone Id")]
public int? ZoneId { get; set; }
public virtual FoodCategory CategoryNameNavigation { get; set; } = null!;
public virtual Zone? Zone { get; set; }
public virtual ICollection<Dish> Dishes { get; set; }
public virtual Dish Dish { get; set; }
}
}
And I have XUnit Test Class within which I am instantiating the class restaurant like so
Restaurant restaurant1 = new Restaurant
{
RestaurantName = "Khana",
RestaurantAddress = "Tokyo",
CategoryName = "Indian",
ZoneId = 2
};
And yet when I try to catch the name with the below method it returns a null exception, see below method
[Fact]
public async Task GetRestaurantByName()
{
Restaurant restaurant = await _repository.GetByName("Khana");
Assert.Equal("Khana", restaurant.RestaurantName);
}
See error 'System.NullReferenceException : Object reference not set to an instance of an object.'
When running the debug I see that restaurant1 doesn't contain the name or any other properties, apart from the 'CategoryNameNavigation', see below screenshot
Or here the autos
restaurant CategoryNameNavigation = null
Futures.Api.Data.Models.Restaurant
CategoryNameNavigation null
Futures.Api.Data.Models.FoodCategory
+ this
{Futures.Api.Data.Sqlite.Test2.SqliteRestaurantTest.FuturesConfigTests}
Futures.Api.Data.Sqlite.Test2.SqliteRestaurantTest.TestWithSqlite
{Futures.Api.Data.Sqlite.Test2.SqliteRestaurantTest.FuturesConfigTests}
As you can see, all the properties (such as RestaurantName) are gone :(
Here is the method GetByName
public async Task<Restaurant?> GetByName(string restaurantName)
{
return await _context.Restaurants
.Include(restaurantRecord => restaurantRecord.Dishes)
.Include(restaurantRecord => restaurantRecord.Zone)
.Include(restaurantRecord => restaurantRecord.CategoryNameNavigation)
.SingleOrDefaultAsync(record => record.RestaurantName.ToLower().Contains(restaurantName.ToLower()));
}
I would be grateful if anyone could hint me to where the issue here, any tips are welcome!
Being quite a newbie to c# I am sure there's something obvious I missed here.

Related

creating an object in ternary operator from query result

I need to create objects to return to front end from data I got from database because the response doesnt contain ALL fields of entity and I also add this [NotMapped] propety AmountOfTenants = t.Apartment.Tenants.Count(), to the response.
If I remove ternary operator here t.ApartmentId != null ? and just create new Apartment every time, then, when a Tenant doesnt have any related Apartmetn to him, then my json response contains Tenant with an Apartment object inside of him, where all values are 0/null, thats why i need ternary, to set Apartment to null if Tenant's ApartmentId is null.
Without ternary it works, but i get that apartment with 0/null values and when i add ternary operator i get this error:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.NewExpression' to type 'System.Linq.Expressions.MethodCallExpression'.
Please help
List<Tenant> tenants = await _context.Tenants.Include(tenant => tenant.Apartment.House)
.Select(t => new Tenant
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
PersonalCode = t.PersonalCode,
Birthday = t.Birthday,
PhoneNumber = t.PhoneNumber,
Email = t.Email,
ApartmentId = t.ApartmentId,
Apartment = t.ApartmentId != null ? new Apartment
{
Id = t.Apartment.Id,
Number = t.Apartment.Number,
Floor = t.Apartment.Floor,
AmountOfTenants = t.Apartment.Tenants.Count(),
AmountOfRooms = t.Apartment.AmountOfRooms,
TotalArea = t.Apartment.TotalArea,
AvailableArea = t.Apartment.AvailableArea,
HouseId = t.Apartment.HouseId,
House = t.Apartment.House
} : null
}).ToListAsync();
----------------------------------------------EDIT
someone asked for Tenant entity:
namespace RestApi.Models
{
public class Tenant
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PersonalCode { get; set; }
public DateTime Birthday { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public long? ApartmentId { get; set; }
public Apartment? Apartment { get; set; }
}
}
--------------------------EDIT
someone asked for Apartment class:
namespace RestApi.Models
{
public class Apartment
{
public long Id { get; set; }
public int Number { get; set; }
public int Floor { get; set; }
public int AmountOfRooms { get; set; }
[NotMapped]
public int AmountOfTenants { get; set; }
public int TotalArea{ get; set; }
public int AvailableArea { get; set; }
public long? HouseId { get; set; }
public House House { get; set; }
public ICollection<Tenant> Tenants { get; set; }
}
}
------------------------------ EDIT
I got a friend to check something similar on his machine:
var tenants = await context.Tenants
.Include(t => t.Apartment).ThenInclude(a => a.House)
.Include(t => t.Apartment).ThenInclude(a => a.Tenants)
.Select(t => new Tenant
{
Id = t.Id,
//etc...
Apartment = t.ApartmentId != null ? new Apartment
{
Id = t.Apartment.Id,
NumberOfTenants = t.Apartment.Tenants.Count(),
//etc...
} : null
}).ToListAsync();
on his machine this new statement inside another new statement works and gives no error
You could try using this, which should eliminate the need to use the NotMapped annotation and you wouldn't need to use the select at that point either.
public class Apartment
{
public long Id { get; set; }
public int Number { get; set; }
public int Floor { get; set; }
public int AmountOfRooms { get; set; }
public int AmountOfTenants { get { return this.Tenants != null ? this.Tenants.Count : 0; } }
public int TotalArea { get; set; }
public int AvailableArea { get; set; }
public long? HouseId { get; set; }
public House House { get; set; }
public ICollection<Tenant> Tenants { get; set; }
}
New Query
List<Tenant> tenants = await _context.Tenants.Include(tenant => tenant.Apartment).ThenInclude(a => a.House).ToListAsync();
Edit
As for an explanation of the error you are getting. It is basically saying that it can't make a new expression inside of a new expression (i.e. creating a new Apartment inside of a new Tenant)
I'm making an assumption about your Apartments class so correct me if I go astray here.
Assumption : A tenant can only have one apartment and an apartment can have multiple tenants.
With that being said you tenant class just needs to reference an Apartment object. The Apartment ID is contained in Apartment object so you're really storing that data twice. If you're looking to add an new tenant to an apartment you don't need the select statement. You might want to do a bullet point summary to clarify your intentions if there is more to this. If you're looking to include all objects within the Tenant class(data from other tables) you can consider lazy loading if your application permits.
public class Tenant
{
[Key]
public long Id { get; set; } //Primary Key
public string FirstName { get; set; }
public string LastName { get; set; }
public string PersonalCode { get; set; }
public DateTime Birthday { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
//public long? ApartmentId { get; set; }
public virtual Apartment Apartment { get; set; }
}
public class Apartment
{
[Key]
public Guid ApartmentID { get; set; } //Primary Key
public virtual List<Tenant> Tenants { get; set; }
}
public class appStuff
{
void AddStuff()
{
var myContext = new _context();
Apartment apartment = myContext.Find(Predicate4YourApartment());
var ten = new Tenant
{
Birthday = DateAndTime.Now,
Apartment = apartment,
FirstName = "John" //add the rest.
};
myContext.TenantsTable.Add(ten);
//This will add the Tenant to the Tenants table and the apartment to the Apartments table if you create a new one
//instead of finding an existing one like shown above
myContext.SaveChanges();
}
void GetStuff()
{
var myContext = new _context();
var myTenant = myContext.TenantsTable.Where(x => x.Apartment.ApartmentID == "DesiredID");
}
}
public class _context : DbContext
{
public _context() : base()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
base.OnConfiguring(optionsBuilder);
}
}

Trying to pass an IEntity to a list constructor and having trouble

I have a customer object that has a list of sales orders. I am trying to create a partial view on my webpage that lets me use radio buttons to click through and see the lists per customer. The issue is that in the customer controller, I need to pass the customer IEntity (via an id) to the constructor.
I tried looking this up but I am new to all this and I don't think I am using the right words or maybe I just don't understand well enough yet to find the answer in other peoples code.
Customer Object (and constructor obviously)
public class Customer : IEntity
{
[Key]
public int CustomerId { get; set; }
public int Id => CustomerId;
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Customer")]
public virtual string Name => $"{FirstName} {LastName}";
[Display(Name = "Address")]
public string Address1 { get; set; }
public string Address2 { get; set; }
[Display(Name = "City")]
public string City { get; set; }
[Display(Name = "State")]
public string State { get; set; }
[Display(Name = "Zipcode")]
public string Zipcode { get; set; }
[Display(Name = "Email")]
public string Email { get; set; }
[Display(Name = "Phone Number (USA)")]
public string UsaPhone { get; set; }
public decimal TotalSales => SalesOrders.Sum(so => so.OrderTotal);
public List<SalesOrder> SalesOrders { get; private set; }
public Customer()
{
SalesOrders = new List<SalesOrder>();
}
}
Code from the Customer Controller where I am getting the error
public ActionResult _customersSalesOrders(int id)
{
var customer = db.Customers.Find(id);
var salesOrderList = new List<SalesOrder>(customer);
return PartialView(salesOrderList);
}
The result is error CS1503 "Argument 1: cannot convert from 'ITP245.Models.Customer' to 'int'
You are creating a new List<SalesOrder>, which means you can only place into it objects that are of type SalesOrder. Your Customer class does not inherit from SalesOrder & cannot be placed into the list.
Maybe you mean to place the customer's sales orders into the list?
If so, your code might look something like this:
public ActionResult _customersSalesOrders(int id)
{
var customer = db.Customers.Find(id);
var salesOrderList = customer.SalesOrders;
return PartialView(salesOrderList);
}

Entity Framework Creates Unwanted Records In Database in one to many relationships

I am using codefirst technique in my MVC ASP.NET application to generate my database. I attempted to Create a one to many relationship between two of my tables, Customer and Room.
I want one customer to be able to book one or more than one room.
The Following is my Customer model:
public class Customer
{
public int id { get; set; }
[Display(Name = "Name")]
public string name { get; set; }
[Display(Name = "Email Address")]
public string email { get; set; }
[Display(Name = "Phone Number")]
public string phoneno { get; set; }
[Display(Name = "Date of Birth")]
public DateTime DOB { get; set; }
[Display(Name = "CNIC")]
public string cnic { get; set; }
public List<int> Roomsid { get; set; }
[Display(Name = "Check In Date")]
public DateTime? checkin { get; set; }
[Display(Name = "Check Out Date")]
public DateTime? checkout { get; set; }
public List<Room> Room { get; set; }
}
And the following is model
public class Room
{
public int id { get; set; }
public int floorno { get; set; }
public string roomclass { get; set; }
public int cost { get; set; }
public string bedcount { get; set; }
public string facilities { get; set; }
public string description { get; set; }
public int? Customerid { get; set; }
}
Let's say when ever I pass a Customer object to my database, entity frameworks creates new Room records even when I define the RoomID for the relevant customer entries.
Let me elaborate, let's say I run the following code:
ApplicationDbContext x = new ApplicationDbContext();
var a = new Customer() { };
var b = new List<Room>() {
new Room { id=2 ,floorno=2, bedcount="2", cost=2, description="2", facilities="2", roomclass="2" },
new Room {id=3 ,floorno=3, bedcount="3", cost=2, description="3", facilities="3", roomclass="3" },
new Room {id=4 ,floorno=4, bedcount="4", cost=4, description="4", facilities="4", roomclass="4" },
};
a.checkin = Convert.ToDateTime("05 Mar 2017");
a.checkout = Convert.ToDateTime("07 Mar 2017");
a.DOB = Convert.ToDateTime("02 Mar 2000");
a.cnic = "asfsgwlkgnwe98hf0";
a.email = "agjw98e98weg98";
a.name = "Äfnan Makhdoom";
a.Rooms = b;
x.Customers.Add(a);
x.SaveChanges();
Or even when I don't define any other parameter except the room id in my variable b, I get additional records created in my Rooms table in my database.
Even when I choose RoomID as 1, it will create a new record with a new RoomID with other fields the same as I define them. Any help?
If all you are trying to do is add a Customer to an existing Room, you could do something like this.
var a = new Customer() { };
var roomWithId3 = x.Rooms.Single(x => x.Id == 3);
a.rooms.Add(roomWithId3);
x.Customers.Add(a);
x.SaveChanges();

ASP.NET MVC 5 Edit Action - How to write to Multiple DB Tables/Models

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.

Invalid Column Name Error When Accessing Navigation Properties From View

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.

Categories

Resources