Want to create a Reviews table and got confused what foreign keys I need to place there. I want the ability of ONE user leave Reviews on the page of SECOND user. here is my current model. What do I need to add there?
REVIEWS
using System;
using System.Collections.Generic;
namespace sellwalker.Models
{
public class Review : BaseEntity
{
public int ReviewId{get;set;}
public string Content{get;set;}
public DateTime CreatedAt{get;set;}
public int UserId{get;set;}
public User Reviewer {get;set;}
}
}
USERS
using System;
using System.Collections.Generic;
namespace sellwalker.Models
{
public class User : BaseEntity
{
public int UserId{get;set;}
public string FirstName{get;set;}
public string LastName{get;set;}
public string Email{get;set;}
public string Password{get;set;}
public string Status{get;set;}
public string ProfilePic {get;set;}
public List<Order> Orders {get;set;}
public List<Product> products {get;set;}
public List<Review> Reviews {get;set;}
public User()
{
Orders = new List<Order>();
products = new List<Product>();
Reviews = new List<Review>();
}
}
}
With current table I can write reviews but I want to link it to the special user. System doesn't let me add one more UserId there so what is the best solution here?
You could go with something like that:
Review Model:
public class Review
{
public int Id { get; set; }
//...
public int ReviewerId { get; set; }
public virtual User Reviewer { get; set; }
public int ReviewedUserId { get; set; }
public virtual User ReviewedUser { get; set; }
}
User Model:
public class User
{
public int Id { get; set; }
//...
public virtual ICollection<Review> WrittenReviews { get; set; } = new HashSet<Review>();
public virtual ICollection<Review> MyReviews { get; set; } = new HashSet<Review>();
}
And then you have to specify the connections using fluent API because EF wont know wich navigation property belongs to wich.
Fluent API Code:
modelBuilder.Entity<Review>()
.HasRequired(x => x.ReviewedUser)
.WithMany(x => x.MyReviews);
modelBuilder.Entity<Review>()
.HasRequired(x => x.Reviewer)
.WithMany(x => x.WrittenReviews);
Related
I have following RecipeModel, IngredientModel and RecipePartModel classes which represent the DTO classes for the frontend user:
public class RecipeModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public IEnumerable<RecipePartModel> RecipeParts { get; set; }
}
public class IngredientModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class RecipePartModel
{
public Guid Id { get; set; }
public IngredientModel Ingredient { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
Here are my entity classes:
public class Recipe : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class Ingredient : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class RecipePart : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public Ingredient Ingredient { get; set; }
public Recipe Recipe { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
My question is - how can I map the Recipe to RecipeModel using AutoMapper? I tried something like this but I assume it is bad, because it just join all the RecipeParts for the whole database, am I correct?
public class DomainProfile : Profile
{
public DomainProfile()
{
CreateMap<Ingredient, IngredientModel>().ReverseMap();
CreateMap<Recipe, RecipeModel>()
.ForMember(x => x.RecipeParts, opt => opt.MapFrom(src => src.RecipeParts));
}
}
To answer your question about how to use AutoMapper to map a type to another type, there are many ways of doing this. Documentation is here: http://docs.automapper.org/en/stable/Getting-started.html.
I wrote a console app and got it working in the quickest way I know possible using your code. When I debug this, and check inside recipeModel, it references a list of RecipePartModels with a single RecipePartModel. Inside that RecipePartModel, it references an IngredientModel.
static void Main(string[] args)
{
var profile = new DomainProfile();
Mapper.Initialize(cfg => cfg.AddProfile(profile));
var recipe = new Recipe
{
RecipeParts = new List<RecipePart>
{
new RecipePart()
{
Ingredient = new Ingredient()
}
}
};
var recipeModel = Mapper.Map<Recipe, RecipeModel>(recipe);
Console.ReadKey();
}
To answer your concern about getting all recipes from the database, if you're using Entity Framework, it depends on if you have lazy loading turned on. Lazy loading ensures that, when you get a recipe from the database, the recipe parts will not be loaded. They will only be loaded when you access the recipe part directly later on in the program flow. Lazy loading is turned on by default so this is the default behaviour. If you turn it off, you've enabled eager loading which loads all recipe parts and in turn their ingredient.
This might help: http://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx.
There is nothing bad about this mapping. In fact you don't even need the ForMember call as this is the default convention. The mapping will simply convert each element in the entity child collection to a corresponding model object.
Of course, whether you load your entities in an efficient manner is another matter. If you load a large amount of Recipe entities, and lazy load the RecipeParts collections for each, you will have a major "SELECT N+1" problem. But this is not the fault of AutoMapper.
So right now my IdentityModels.cs looks like this:
using System;
using System.Data.Entity;
using System.Security.Claims;
using System.Security.Policy;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Leepio.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
//company
[Display(Name = "Virkshomedsnavn")]
public string CompanyName { get; set; }
public string ZipCode { get; set; }
//company
public int NrEmployees { get; set; }
//company
public string WorkField { get; set; }
public string Language { get; set; }
//Student
public string University { get; set; }
public string StudyProgramme { get; set; }
public int Semester { get; set; }
public string GraduationDate { get; set; }
//
[AllowHtml]
public string Description { get; set; }
//Student
public string Skills { get; set; }
public string Website { get; set; }
public string Address { get; set; }
//Student
[DataType("date")]
public DateTime DateOfBirth { get; set; }
public virtual ICollection<Blog> Blogs { get; set; }
public virtual ICollection<Application> Applications { get; set; }
public virtual ICollection<Project> Project { get; set; }
public virtual IList<Experience> Experience { get; set; }
public virtual ICollection<Skill> Skill { get; set; }
public virtual IList<Education> Education { get; set; }
public virtual IEnumerable<Experience> ExperienceOrdered { get { return Experience.OrderByDescending(e => e.EndYear); } }
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;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public System.Data.Entity.DbSet<Leepio.Models.Project> Projects { get; set; }
public System.Data.Entity.DbSet<Leepio.Models.NewsletterMails> NewsletterMails { get; set; }
public System.Data.Entity.DbSet<Skill> Skill { get; set; }
public System.Data.Entity.DbSet<Leepio.Models.Application> Applications { get; set; }
public System.Data.Entity.DbSet<Leepio.Models.Contract> Contracts { get; set; }
public System.Data.Entity.DbSet<Leepio.Models.Experience> Experience { get; set; }
public System.Data.Entity.DbSet<Leepio.Models.Blog> Blogs { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Education> Educations { get; set; }
}
}
What I want to do is, through code (migrations), add a new table as an extensions to the user table (AspNetUsers) through code as to when I register a new user with data taken from facebook, the data that I don't need for the main Users Table can go to the second one, lets say call it "FBData" with the appropriate userId.
To put it in perspective:
Register user would add userID, FirstName, LastName, Email to Users table and at the same time add ID and Locale, Gender in the FBData table.
Microsoft Identity uses AspNetUserClaims to store all additional data you specified in your ApplicationUser model class. When you use external Authentication Provider such as Facebook, Google etc. a new entry is created in AspNetUserLogins table to store the ProviderKey. This key is used when user is logging in to your application for the second time.
For my understanding your plan to create a FBData table is not necessarily good. How about you add Google Authentication after? Will you create a GData table?
The best option would be in your AccountController in ExternalLoginCallback (function triggered when the user is redirected back from your external authentication provider to your page) map what you received from facebook to your ApplicationUser and maybe redirect the the user to the registration form with pre-populated fields to finish the registration cycle. All the trash data you can store if you like in a separate table AspNetUserClaims_External but you have to model it first.
Check:
https://github.com/IdentityServer/IdentityServer4.Samples/tree/dev/Quickstarts/4_ImplicitFlowAuthenticationWithExternal
There are a few things you need to do. First you can create a FbData class with the properties you need in that table. Then add the properties you need to the ApplicationUser Class. You need the foreign key for the FbData table
public int FbDataId { get; set; }
and you also need to add the virtual property for the table:
public virtual ICollection<FbData> FbDatas { get; set; }
Last in your IdentityContext add you DbSet for your table:
public DbSet<FbData> FbData { get; set; }
Run the application on a clean db to see your changes reflected..
You can also override the OnModelCreating class in your IdentityContext to make any modifications to the Generated Identity Tables.
I have two models
class Employee {
[Key]
public int ID {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int EmploymentID {get;set;}
[Required, ForeignKey("Employment")]
public virtual Employment Employment {get;set;}
}
class Employment {
[Key, ForeignKey("Employee")]
public int ID {get;set;}
public string Department {get;set;}
public int OfficePhone {get;set;}
public int EmployeeID {get;set;}
public virtual Employee Employee {get;set;}
}
basically each employee has employment information in the Employment class. I'm not sure if I need the [Required] annotation there, and I don't know if I'm putting the [ForeignKey] annotation in the right spot either.
The problem is, when I try to create a new scaffolded item, it gives me this error:
Unable to determine the principal end of an association between the types 'Bla.Models.Employee' and 'Bla.Models.Employment'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Thank you for the help
EDIT
assume the Employee model has the following instead:
class Employee {
[Key]
public int ID {get;set;}
public string FirstName {get;set;}
//note the return value is an ICollection object
public ICollection<LastName> LastName {get;set;}
public int EmploymentID {get;set}
public virtual Employment Employment {get;set;}
}
and the Employment model remains the same,
and the LastName field has the following class
class LastName {
public string EmployeeLastName {get;set;}
//assuming last name can change, and employee had a different last name at some point
public int year{get;set;}
}
is it incorrect to make the LastName class, a model? or should it remain a model?
how can I make it so that its just a resource class (i.e. not a model to be made into a table)
further, will this kind of thing break the relationships between employee/employment models?
because I'm still getting the error and not sure why; by the way, I have many of these classes like the "LastName" example and they are all currently under models, and I'm not sure if they should be a model or some resource class, and if so, I don't know where they are supposed to go
also heres my dbcontext
public class MyDbContext : DbContext
{
public MyDbContext()
: base("MyDbContext")
{
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Employment> Employments { get; set; }
public DbSet<BasicAddress> Adresses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<BasicDate> BasicDate { get; set; }
public DbSet<BasicPhoneNumber> BasicPhoneNumber { get; set; }
public DbSet<EmployeeIdentification> EmployeeIdentification { get; set; }
public DbSet<FederalIdentification> FederalIdentification { get; set; }
public DbSet<JobTitle> JobTitle { get; set; }
public DbSet<LastName> LastName { get; set; }
public DbSet<MaritalStatus> MaritalStatus { get; set; }
public DbSet<OfficeLocation> OfficeLocation { get; set; }
}
everything other than employee and employment is a basic class (like the LastName class), I'm not sure if they should be with "models" and made into tables or just be regular classes on the side. If they shouldn't be made into tables, where should they go in the project?
thank you again for the help! (please let me know if anything needs clarification)
Employment cannot exist with Employee. As per the law of foreign relationship the Employment must not be saved without Employee. Thus with the help of [ForeignKey] notation you need to advise Employment that Employee relation should be maintained.
Employment is dependent on Employee. Thus you got to tell Employment that you are got to have a linking with the principal.
Thus dependent class must have Employee id reference.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace TestConsole
{
public class Employee
{
public int ID { get; set; }
[Required]
public string FirstName { get; set; }
public string LastName { get; set; }
[Required]
public virtual Employment EmploymentDetails { get; set; }
}
public class Employment
{
[Key, ForeignKey("Employee")]
public int ID { get; set; }
public string Department { get; set; }
public int OfficePhone { get; set; }
public virtual Employee Employee { get; set; }
}
internal class Program
{
public TestConsoleDbContext MyDbContext { get; set; }
public Program()
{
MyDbContext = new TestConsoleDbContext();
}
private static void Main(string[] args)
{
var program = new Program();
var records = from p in program.MyDbContext.Employees
select new { p.EmploymentId, p.LastName, p.Employment.Department };
foreach (var r in records)
{
Console.WriteLine("EmploymentID: {0} {1} Department: {2}", r.EmploymentId, r.Department);
}
Console.ReadLine();
}
}
}
using System.Data.Entity;
namespace TestConsole
{
internal class TestDbContext : DbContext
{
public IDbSet<Employee> Employees { get; set; }
public IDbSet<Employment> Employments { get; set; }
}
}
I don't think Employment field needs the Required attribute and the ForeignKey attribute must be applied to the EmploymentID field.
Forewarning: I know approximately nothing when it comes to MVC/Entity Framework/Linq queries.
I'm having an issue when I try to query the database. Here's the query I'm using:
int? UserId = db.StudentModel
.Where(c => c.UserName == certUserName)
.Select(c => c.UserId)
.FirstOrDefault();
When it searches the database, it successfully retrieves the UserId.
The problem is that I then use the following query:
CompletionsModel student = db.Completions.Find(UserId);
When I do this, it throws an inner exception that states
{"Invalid column name 'UserProfile_UserId'."}
The weird thing is that when I go to my code and mouse over the 'db' part of the command to see what data it's holding, it has CourseModel, StudentModel, and Completions (though the model's actual filename is CompletionsModel - is that a clue?), so it seems like they're linked properly.
Here's the code for my three models and the database context.
CompletionsModel (UserProfile is white text in my code; not sure why it's teal here Same with UserId and CompletionDate):
[Table("Completion")]
public class CompletionsModel
{
[Key]
public int UserId { get; set; }
public string PRD_NUM { get; set; }
public DateTime CompletionDate { get; set; }
public virtual CourseModel PRD { get; set; }
public virtual StudentModel UserProfile { get; set; }
}
CourseModel:
[Table("PRD")]
public class CourseModel
{
[Key]
public string PRD_NUM { get; set; }
public string PRD_TIT { get; set; }
//because any number of students can be enrolled in one course
public virtual ICollection<CompletionsModel> CompletionsModel { get; set; }
}
StudentModel:
[Table("UserProfile")]
public class StudentModel
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<CompletionsModel> CompletionsModel { get; set; }
}
DBContext:
public class ClassContext : DbContext
{
public ClassContext()
: base("DefaultConnection")
{
}
public DbSet<StudentModel> StudentModel { get; set; }
public DbSet<CompletionsModel> Completions { get; set; }
public DbSet<CourseModel> CourseModel { get; set; }
}
And finally, an image of my database layout - maybe this will help things along, too:
I'm too at the beginning of Entity Framework, but what does irritate me is, that you have kind of foreign key relationship between Completion and UserProfile, without really defining, a int column as foreign key in your Completion Table.
You could try to change
public virtual StudentModel UserProfile { get; set; }
to something like
public virtual int StudentId { get; set; }
[ForeignKey("StudentId")]
public virtual StudentModel UserProfile { get; set; }
The same for PRD
But maybe a change in your database is not what you want.
What you could also do, is to remove these lines
public virtual CourseModel PRD { get; set; }
public virtual StudentModel UserProfile { get; set; }
Hope that helps.
EDIT:
I guess the problem is, that you are missing the ForeignKey Attribute at your UserProfile property in your Completions table.
So use
[ForeignKey("UserId")]
public virtual StudentModel UserProfile { get; set; }
instead of
public virtual StudentModel UserProfile { get; set; }
if the UserIds are representing the same user
I'm making my first steps with Fluent Api and I'm trying to understand how to do the following: This is my model.
public class Person
{
public int Id {get ; set;}
public List<View> Viewers {get; set;}
}
public class View
{
public int Id {get; set;}
public Person Viewer {get; set;}
}
This is a simplification of my model, I want to keep track of Person's that access some others profiles. I want to know for every person who has seen their profile.
I have tried this:
var view = modelBuilder.Entity<View>();
view.HasKey(v=>v.Id);
var person = modelBuilder.Entity<Person>();
person.HasKey(r => r.Id);
person.HasMany(t => t.Viewers).WithRequired(t=>t.Viewer);
I know this seems super silly, but from my code I want to be able to navigate:
Person -> Viewers (let's take viewer 1) -> Viewers, and so on...
Is this the right approach?
Thanks in advanced!
try this
public class Person
{ [Key]
public int Id { get; set; }
public ICollection<View> Viewers { get; set; }
}
public class View
{ [Key]
public int Id { get; set; }
public int ViewerId { get; set; } //this is a ForeingKey
public Person Viewer { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<View> Views { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<View>().HasRequired(a => a.Viewer).WithMany().HasForeignKey(a => a.ViewerId);
base.OnModelCreating(modelBuilder);
}
}
you may do the same with DataAnnotation Attributes and then it does't need to use Fluent API.
public class View
{
public int Id { get; set; }
public int ViewerId { get; set; }
[ForeignKey("ViewerId")] // here is a foreignkey property
[InverseProperty("Viewers")] // here is a navigation property in Person class
public Person Viewer { get; set; }
}