I created local database using EF core and code-first method.
The db imitates library, so I have 3 simple tables: users, books and reservations.
Issue occurs when I want to get nested data like find one book and get its reservation.
I think I should be able to use
List<Reservation> reservations = book.Reservations;
but I have to use
List<Reservation> reservations = libraryContext.Reservations.
Where(r=> r.Book == book).ToList();
But the main reason I need help is this fragment
BookReservationsModel bookReservationsModel = new BookReservationsModel
{
BookTitle = book.Title,
Reservations = reservations
};
// I want to display emails in View.
for (int i = 0; i < bookReservationsModel.Reservations.Count; i++)
{
Debug.WriteLine(bookReservationsModel.Reservations[i].User.Email);
}
I cannot get access to users because they are nulls. In database everything is stored as it should be (correct ids). Of course I could copypaste certain emails to new created list but it's inefficient and I know I should be able to use it that way. I worked before with EF for Framework and I tried google the problem but couldn't find the solution.
Models and context code.
public class User
{
[Key]
public int UserID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Salt { get; set; }
[Required]
public string Password { get; set; }
public ICollection<Reservation> Reservations { get; set; }
}
public class Book
{
[Key]
public int BookID { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Author { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; }
[Required]
public string Description { get; set; }
public ICollection<Reservation> Reservations { get; set; }
}
public class Reservation
{
[Key]
public int ReservationID { get; set; }
[Required]
public DateTime ReservationDate { get; set; }
[Required]
public int UserID { get; set; }
[ForeignKey("UserID")]
public User User { get; set; }
[Required]
public int BookID { get; set; }
[ForeignKey("BookID")]
public Book Book { get; set; }
}
public class LibraryContext : DbContext
{
public LibraryContext(DbContextOptions options) : base(options) { }
public DbSet<User> Users { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Reservation> Reservations { get; set; }
}
Attempting to use a navigation property on the book entity without doing either of the following will result in the property being null.
Including the property before materializing the entity with .First()/.Single()
https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager
Configuring EFCore to AutoInclude navigation properties by default.
https://dotnetcoretutorials.com/2021/03/07/eager-load-navigation-properties-by-default-in-ef-core/
I suggest when querying for the book to use the Include and ThenInclude methods so the Reservations and Users are populated.
var book = await libraryContext.Books
.Include(x => x.Reservations)
.ThenInclude(x => x.User)
.SingleAsync(x => x.BookID == myBookId);
Related
I have a case scenario with two tables References and Products alreading containing many entries which can be dynamically related on demand.
public class Reference
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ReferenceId { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public virtual ICollection<Product> ManyProducts { get; set; }
public Reference() {}
}
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
[ForeignKey("Reference")]
public Guid ReferenceId { get; set; }
public virtual Reference OneReference { get; set; }
public Product() {}
}
When a user ask to link a reference to a product I simply do :
product.ReferenceId = reference.ReferenceId ;
await context.SaveChangesAsync() ;
The entry in Products table is updated correctly, but when I try to access a reference's related data, it does not retrieve any ?? After eager loading :
var reference = await context.References
.Include(r => r.ManyProducts)
.SingleAsync(r => r.ReferenceId == referenceId) ;
or explicit loading :
var reference = await context.References.FindAsync(referenceId) ;
await context.Entry(reference).Collection(s => s.ManyProducts).LoadAsync() ;
reference.ManyProducts is empty. So I have to do something like this :
var reference = await context.References.FindAsync(referenceId) ;
var products = await context.Products.Where(l => l.ReferenceId == referenceId).ToListAsync() ;
result.ManyProducts = products ;
which works fine, but I would like to understand why ?
I´m using DataAnnotation
Sample
public class spread
{
[Key]
public int spreadid { get; set; }
[Required]
public DateTime insertdate { get; set; }
[Required]
public int exchangeid { get; set; }
[ForeignKey("exchangeid"), Display(Name = "Exchange origem")]
public virtual exchange exchange { get; set; } // One to one
[ForeignKey("spreadid")]
public virtual ICollection<spreadhelper> spreadhelper { get; set; } // One to many
}
public class spreadhelper
{
[Key]
public int spreadhelperid { get; set; }
[Required]
public int spreadid { get; set; }
[Required]
public int exchangeid { get; set; }
[ForeignKey("exchangeid"), Display(Name = "Exchange")] // One to one
public virtual exchange exchange { get; set; }
[Required, Range(0, 200)]
public decimal spreadvalue { get; set; }
}
one to one - sample
public class exchange
{
[Key]
public int exchangeid { get; set; }
[Required]
public DateTime insertdate { get; set; }
[Required, MaxLength(50)]
public string name { get; set; }
[MaxLength(128)]
public string token { get; set; }
}
One to many sample
i am designing a system and one of my entity has one to many relation as shown below.
public class Product
{
public int Id { get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
public Product Product { get; set; }
}
competitorProduct indicates that product has a equivalent which is sold by different store. should i define one-to-many relation as shown above or below? which one is correct?
public class Product
{
public int Id { get; set; }
public virtual ICollection<CompetitorProduct> CompetitorProducts{ get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
}
Assuming it is a one to many relationship (what would happen if a competitor product was competing with more than one of your products for example) you can do both and add in a foreign key as well.
public class Product
{
public int Id { get; set; }
public virtual ICollection<CompetitorProduct> CompetitorProducts { get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
You can then set up your relationship using fluent API as so:
modelBuilder.Entity<CompetitorProduct>(entity =>
{
entity.HasOne(e => e.Product)
.WithMany(e => e.CompetitorProducts)
.HasForeignKey(e => e.ProductId)
.HasConstraintName("FK_ComptetitorProduct_Product");
});
This way you can access the competitor products from the product and the product from the competitor products.
Here is a quick example of a ecommerce site I have worked on and how we did table relations.
I removed a bunch of the fields so you can see what you really need. Once to make relations and run Add-Migration EF will handle the FK constraints for you as long as you identified them in models like how I have below.
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
Active = true;
CreateDateTimeUtc = DateTime.UtcNow;
ModifiedDateTimeUtc = DateTime.UtcNow;
}
[StringLength(500)]
public string FirstName { get; set; }
[StringLength(500)]
public string LastName { get; set; }
[StringLength(1000)]
public string Address { get; set; }
[StringLength(100)]
public string Unit { get; set; }
[StringLength(250)]
public string City { get; set; }
[StringLength(25)]
public string State { get; set; }
[StringLength(20)]
public string ZipCode { get; set; }
//This will give access to a list of child carts a user could have
[Index]
public bool Active { get; set; }
public virtual ICollection<Cart> Carts { get; set; }
// Account Profile Image
public byte[] ProfileImage { get; set; }
[StringLength(500)]
public string ProfileFilename { get; set; }
[StringLength(100)]
public string ProfileMimeType { get; set; }
}
[Table("Cart", Schema = "dbo")]
public class Cart : AbstractTable
{
public Cart()
{
IsComplete = false;
}
//This create relation to user table where I can get one unique user.
[StringLength(128)]
[ForeignKey("ApplicationUser")]
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
//These link us to child tables of Cart where we can get a LIST of the items below
public virtual ICollection<CartCategory> CartCategories { get; set; }
public virtual ICollection<CartItem> CartItems { get; set; }
// Marked when a payment/receipt is generated based off of this cart
public bool IsComplete { get; set; }
}
[Table("CartItem", Schema = "dbo")]
public class CartItem : AbstractTable
{
//This will return one unique cart id and let us access it as the parent record
[ForeignKey("Cart")]
public Guid CartId { get; set; }
public virtual Cart Cart { get; set; }
// Signifies if this was paid for in a receipt
public bool IsComplete { get; set; }
public virtual ICollection<CartItemCustomField> CustomFields { get; set; }
}
I have a UserProfile class
public class UserProfile
{
public UserProfile()
{
}
public UserProfile(string userId)
{
AppUserId = userId;
}
[Key]
public int UserProfileId { get; set; }
public string AppUserId { get; set; }
public ICollection<Blog> AuthoredBlogs { get; set; }
public ICollection<Blog> SubscribedBlogs { get; set; }
//other properties removed for brevity
}
And the associated Blog class
public class Blog
{
[Key]
public int BlogId { get; set; }
[ForeignKey("BlogAuthor")]
[Index("IX_AuthorIndex", 1, IsClustered = false, IsUnique = false)]
public int AuthorId { get; set; }
[Required]
public Author BlogAuthor { get; set; }
[Required(ErrorMessage = "A blog name is required")]
public string BlogName { get; set; }
public string BlogIconUrl { get; set; }
public List<BlogPost> BlogPosts { get; set; }
public EquipmentCategory EquipmentCategory { get; set; }
public EquipmentType EquipmentType { get; set; }
public ICollection<int> BlogReaderIds { get; set; }
public Blog(string name, Author author)
{
BlogName = name;
BlogAuthor = author;
EquipmentType = EquipmentType.NoSearch;
EquipmentCategory = EquipmentCategory.NoSearch;
}
public Blog()
{
EquipmentType = EquipmentType.NoSearch;
EquipmentCategory = EquipmentCategory.NoSearch;
}
}
I am having a hard time figuring out how to model the two collections in UserProfile (AuthoredBlogs and SubscribedBlogs) with Blog class. Having those two collections in UserProfile would require two FK associations to Blog but I just dont see how that can/should work.
A UserProfile can subscribe to and author many Blogs. But the Blog class can only have one author and either a list of subscribed UserProfiles or , as I have it here, a list of the subscriber's UserProfileId's.
I cant get it to work, the code first updates are failing to deploy to the db due to the FK association issues.
Any help appreciated.
These models annotations will create one-to-many relation between autor and blogs and many-to-many relation between blogs and subscribers via autocreated, shadow table.
public class UserProfile
{
//other stuff...
[InverseProperty("Autor")]
public ICollection<Blog> AuthoredBlogs { get; set; }
[InverseProperty("SubscribedUserProfiles")]
public ICollection<Blog> SubscribedBlogs { get; set; }
}
public class Blog
{
//other stuff..
public ICollection<UserProfile> SubscribedUserProfiles { get; set; }
public UserProfile Autor { get; set; }
[ForeignKey("Autor")]
public int AutorId { get; set; }
}
I am building a Code-First, Many-To-Many relationship between my ApplicationUser class and a Lesson class. When the model is created, Entity Framework builds the two tables and the intersecting pivot table. However, neither table seems to take in data from the pivot table (LessonApplicationUsers). Both List variables do not seem to hold either the list of Students or the list of Lessons. Both entities i'm trying to marry up already exist in the database
ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string Address { get; set; }
public int? Age { get; set; }
public ClassLevel? ClassLevel { get; set; }
public string FirstName { get; set; }
public int? Height { get; set; }
public string LastName { get; set; }
public string MobileNumber { get; set; }
public string Postcode { get; set; }
public string Town { get; set; }
public int? Weight { get; set; }
public ApplicationUser()
{
Lessons = new List<Lesson>();
}
public ICollection<Lesson> Lessons { get; set; }
}
Lesson Class
public class Lesson
{
[Key]
public int LessonID { get; set; }
public LessonType ClassType { get; set; }
public ClassLevel? ClassLevel { get; set; }
public DateTime ClassStartDate { get; set; }
public DateTime ClassEndDate { get; set; }
public float ClassCost { get; set; }
public int? InstructorID { get; set; }
public Lesson()
{
Students = new List<ApplicationUser>();
}
public ICollection<ApplicationUser> Students { get; set; }
public enum LessonType {Group,Private}
}
My DBContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Lesson> Lessons { get; set; }
public DbSet<ApplyViewModel> Applications { get; set; }
And finally, the code i'm using to add in the pivot table data. This is activated when the user presses a button on the booking form.
public ActionResult BookUser()
{
//Gather required variables
ApplicationUser user = db.Users.First(i => i.UserName == User.Identity.Name);
int classID = int.Parse(Request.Form["classID"]);
using (db)
{
var editedLesson = db.Lessons.Single(s => s.LessonID == classID);
db.Lessons.Attach(editedLesson);
var editedUser = db.Users.Single(s => s.Id == user.Id);
db.Users.Attach(editedUser);
editedLesson.Students.Add(editedUser);
db.SaveChanges();
}
return View("Index");
When I try and run it, when i press my book button, it runs through the code and executes. checking the database it has indeed inserted the key values into the pivot table. When i load the model of the lesson to view its details, the Student attribute has a count of 0. I've been at this for days and i've got the feeling i'm missing something kickself simple....but i've gone over it a dozen times and can't see what i'm doing wrong...
Mark your lists with virtual to enable lazy loading. Also is not required to initialize the lists Lessons = new List<Lesson>();
In my project i need to add Sales leads to the data context. The sales person user adds the leads and I need to send the email to manager for the Lead.
public partial class Lead
{
public Lead()
{
this.LeadActivities = new HashSet<LeadActivity>();
}
public long LeadID { get; set; }
public System.DateTime CreatedAt { get; set; }
public long CompanyID { get; set; }
public long ProductID { get; set; }
public long CreatedByUserID { get; set; }
public string Remarks { get; set; }
public LeadStatusEnum StatusID { get; set; }
public virtual Company Company { get; set; }
public virtual Product Product { get; set; }
public virtual User User { get; set; }
public virtual ICollection<LeadActivity> LeadActivities { get; set; }
}
[Serializable]
public partial class Person
{
public Person()
{
this.Contacts = new HashSet<Contact>();
this.Users = new HashSet<User>();
}
public long PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Designation { get; set; }
public Nullable<int> Gender { get; set; }
public Nullable<int> Title { get; set; }
public int StatusID { get; set; }
public string Thumbnail { get; set; }
public string Email { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<User> Users { get; set; }
}
In the above entity, I have property UserID that is associated to Person table through CreatedByUserID. When I add the new lead, by following code, the User field remains null. Do I need to reconstruct it? if yes then how.
Edit1
Entity Creation is done by following code
Entity = new Model.Lead
{
CreatedAt = DateTime.Now,
CreatedByUserID = SessionManagement.GeneralSession.UserDetail.UserID
};
Entity.CreatedAt = Convert.ToDateTime(txtTimestamp.Value);
Entity.CompanyID = Convert.ToInt64(ddlCompany.SelectedValue);
Entity.CreatedByUserID = Convert.ToInt64(ddlUser.SelectedValue);
Entity.ProductID = Convert.ToInt64(lstProducts.SelectedValue);
Entity.Remarks = txtRemarks.Text;
DataSource.Leads.Add(Entity);
DataSource.SaveChanges();
Virtual lazy loading only works with proxy instances. Since you're explicitly constructing your Lead entity, lazy loading of the User navigation property after inserting the entity will not work.
Instead, you should use the DbSet.Create method to new up an instance of the derived proxy type. Then perform your insert, which will attach to the context, and lazy loading will subsequently work.
Alternatively, you can use your existing POCO, perform the insert and then fetch your inserted entity as its proxy from the DbSet by using the DbSet.Find method.
You should also check and make sure your foreign key id and navigation properties are correctly mapped, since properties CreatedByUserID and User would not be automatically associated by convention.