mvc code first modelling multiple relationships - simple example - asp.net-mvc

I have two Entities in an MVC app:
public class User
{
public int ID {get; set;}
public string Forename {get; set;}
public string Surname {get;set;}
}
public class SubGroup
{
public int ID {get;set;}
public string Name {get;set;}
}
and I want to use Code First to create a relationship between them. The relationship is that each user can be a member of 0 or more subgroups.
Please can someone suggest the best way to do this because I have seen a few examples on SO and I'm confused. I've seen stuff about implementing ICollections in a class, other stuff about using the fluent API.

ICollections approach:
UPDATE:
If I understand correctly you have many-to-many relationship (group have many users, user have can be in many groups), in that case code should be like this:
public class RelationExampleContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<SubGroup> SubGroups { get; set; }
}
public class User
{
[Key]
public int ID { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public ICollection<SubGroup> SubGroups { get; set; }
}
public class SubGroup
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
Adding Users to groups example:
var db = new RelationExampleContext();
var subGroups = new List<SubGroup>
{
new SubGroup() {Name = "Subgroup1"},
new SubGroup() {Name = "Subgroup2"}
};
var users = new List<User>
{
new User()
{
Forename = "Forename1",
Surname = "Surname1",
SubGroups = new List<SubGroup>
{
subGroups.First(),
subGroups.Last()
}
},
new User()
{
Forename = "Forename2",
Surname = "Surname2",
SubGroups = new List<SubGroup>
{
subGroups.First()
}
}
};
foreach (var subGroup in subGroups)
{
db.SubGroups.Add(subGroup);
}
foreach (var user in users)
{
db.Users.Add(user);
}
db.SaveChanges();

I guess User and SubGroup has the same Tables in database
you need create relation table between this tables (fields User_id, Group_id)
model for new entity like UserGroups
and then you can create composition in User class

Related

Navigation property not getting filled on lazy loading

I am trying my hands-on on mvc examples given on Mvc Offical Site.
Here i have 3 Models Student,Course and Enrollment where there is an one to many relationship on Course and Enrollment entities and many to one relationship on Enrollment and student.
The models for Student,Course and Enrollment are as follows with the navigation properties marked as "virtual" since i need to perform lazy binding
public class Student
{
public int StudentID { get;set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual IEnumerable<Enrollment> Enrollments { get; set; }
}
In the same way i have my course model
My Enrollment Model
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
// public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
I am using Code-First Technique With EF 5.My DB Context Class is as follows
public class SchoolContext:DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
By Scaffolding I have generated all my views .On Click of my details Action link I have a controller function that is called
public ActionResult Details(int id = 0)
{
db.Configuration.LazyLoadingEnabled = true;
db.Configuration.ProxyCreationEnabled = true;
Student student = db.Students.Find(id);
// db.Entry(student).Reference(p => p.Enrollments).Load();
IEnumerable<Enrollment> s= student.Enrollments;
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Here the problem is when the Find() method is called in the controller the navigation property in Student Class model is null.But there is data in DB corresponding to the id that is being passed.In short,the navigation property is not returning data(null).
You have to materialize the entities to get a rid of the current lazy loading in the query:
Student student = db.Students.Find(id).ToList();
This will fix your problem without ToList() after db.Students.Find(id); = will returns the generated object from the dynamic proxy.
Second problem you have a casting bug:
IEnumerable<Enrollment> enrollments = student.Enrollments; // is wrong
should be:
ICollection<State> enrollments = student.Enrollments;
or
var enrollments = student.Enrollments;

What is the right way to create related entities with many-to-many relationships

I am new to Entity Framework. Just wondering what is the right way to create related entities with many-to-many relationships. I always get this error when debugging: "Object reference not set to an instance of an object".
Take the following as an example:
Below is the data model, a Club can have multiple Members, a Member can join multiple clubs
public class Club
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Member> Member { get; set; }
}
public class Member
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Club> Club { get; set; }
}
I tried to create a new Club and add a new Member to it (Neither object is existing data in the DB):
Member member = new Member();
Club club=new Club();
member.Name = "Amy";
club.Name="NBA Fans";
member.Club.Add(club); //Error: Object reference not set to an instance of an object
db.Member.Add(member);
db.SaveChanges();
Can anyone please provide the right way to do this?
Thanks a lot
You can try this
Edit :
Follow this link Saving Many to Many relationship data on MVC Create view
public class Club
{
public int ClubId { get; set; }
public string Name { get; set; }
}
public class Member
{
public int MemberId { get; set; }
public string Name { get; set; }
}
Edit Class :
public class ClubMembers
{
public int ClubMember { get; set; }
public List<Member> Member { get; set; }
public List<Club> Club { get; set; }
}
List<Club> clubList=new List<Club>();
Club club = new Club();
club.ClubId=1;
club.Name="Club1";
clubList.Add(club);
List<Member> memberList = new List<Member>();
Member member = new Member();
member.MemberId = 1;
member.Name = "Govinda";
memberList.Add(member);
ClubMembers clubMembers=new ClubMembers();
clubMembers.Club = clubList;
clubMembers.Member=memberList;
foreach (var item in clubMembers.Club)
{
//do code for club entity
}
foreach (var item in clubMembers.Member)
{
//do code for member entity
}

Select multiple table with Linq to Sql

I have two tables. There is one-to-many relationship between these tables.I want to select Company table and BankAccount List table (for appropriate CompanyID).
How can I do it with Linq-to-Sql?
public class Company
{
// My Fields
[Key]
public Guid ID { get; set; }
public string FullName { get; set; }
// My virtual properties FOR relationships(one-to-one,one-to-many etc.).
public virtual List<BankAccount> BankAccounts { get; set; }
}
and
public class BankAccount
{
// My Fields
//[ScaffoldColumn(false)]
[Key]
public Guid ID { get; set; }
[ForeignKey("Companies")]
public Nullable<Guid> CompanyID { get; set; }
public string BankName { get; set; }
// My virtual properties FOR relationships(one-to-one,one-to-many etc.).
public virtual Company Company { get; set; }
}
I write this as follow, but I didn't like it
List<List<BankAccount>> bankaccounts = new List<List<BankAccount>>();
foreach (var comp in companyRepository.Companies)
{
List<BankAccount> banks = new List<BankAccount>();
foreach (var bank in bankRepository.BankAccounts)
{
if (comp.ID == bank.CompanyID)
{
banks.Add(bank);
}
}
bankaccounts.Add(banks);
banks = new List<BankAccount>();
}
I think the following will yield the same result.
var bankaccounts = companyRepository.Companies.Select(c => c.BankAccounts)
.ToList();
If you are using entity framework, you can eagerload the 'BankAccounts' property to minimize DB calls.
Hope this helps.

Many-to-many Entity framework and repository pattern Insert/Update

I am using EF code first approach with fluent api. I am having one registration form in my application where in registering candidate can select multiple options from dropdown(Interested In Dropdown on Sign-Up form) that has a predefined set of options (which may increase in future but the chances are very rare). When the user submits the form I want to save this records to database. So I created following entities.
Participant Class where the registering candidates information will be saved
public class Participant
{
public Participant()
{
Interests = new Collection<Interest>();
}
[Key, ForeignKey("User")]
public int Id { get; set; }
[DisplayName("First Name")]
[StringLength(50, ErrorMessage = "First name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in first name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
[StringLength(50, ErrorMessage = "Last name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in last name")]
public string LastName { get; set; }
[Required(ErrorMessage = "You must indicate your full birthday")]
[DisplayName("Birthday")]
[DataType(DataType.DateTime)]
public DateTime BirthDate { get; set; }
[DisplayName("Gender")]
[Required(ErrorMessage = "You must select gender")]
public int Gender { get; set; }
public string Address { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
[DisplayName("Zip code")]
[StringLength(10, ErrorMessage = "Zip code cannot be more than 10 characters")]
public string ZipCode { get; set; }
public string Mobile { get; set; }
public string PhotoUrl { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
public string MedicalConditions { get; set; }
}
Interest Class from where the Interested In dropdown on Sign-up form will get populate The user can select multiple options from the Interested In dropdown
Interest Class
public class Interest
{
public Interest()
{
Participants = new Collection<Participant>();
}
public int Id { get; set; }
public string InterestName { get; set; }
public virtual ICollection<Participant> Participants { get; private set; }
}
To hold each participants interest I created a ParticipantInterests table in DB with following schema. ParticipantInterests Id (PK) ParticipantId (FK from Participants table) InterestId (FK Interests table)
I added public virtual ICollection Participants { get; set; } in Interest model and
public virtual ICollection Interests { get; set; } in Participant model to form Many-To-Many association.
My Data Context class is as follows
public class STNDataContext : DbContext
{
public DbSet<Participant> Participants { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Interest> Interests { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<SecurityQuestion> SecurityQuestions { get; set; }
public DbSet<Tour> Tours { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Participant>()
.HasKey(p => p.Id);
modelBuilder.Entity<User>()
.HasOptional(u => u.Participant)
.WithRequired();
modelBuilder.Entity<Participant>()
.HasMany(p => p.Interests)
.WithMany(i => i.Participants)
.Map(m =>
{
m.ToTable("ParticipantInterests");
m.MapLeftKey("ParticipantId");
m.MapRightKey("InterestId");
});
modelBuilder.Entity<User>().HasRequired(u => u.Role);
modelBuilder.Entity<Participant>().HasRequired(p => p.Country);
modelBuilder.Entity<User>().HasOptional(u => u.SecurityQuestion);
}
public virtual void Commit()
{
base.SaveChanges();
}
Controller Action Code
public virtual ActionResult Register(StudentRegisterViewModel studentRegisterViewModel)
{
if (ModelState.IsValid)
{
if (_userService.IsUserExists(studentRegisterViewModel.Participant.User) == false)
{
studentRegisterViewModel.Participant.User.Username = studentRegisterViewModel.Username;
studentRegisterViewModel.Participant.User.Email = studentRegisterViewModel.Email;
studentRegisterViewModel.Participant.User.DateCreated = DateTime.Now;
studentRegisterViewModel.Participant.User.Id = 3;
studentRegisterViewModel.Participant.User.IsApproved = false;
studentRegisterViewModel.Participant.User.RoleId = 2;
studentRegisterViewModel.Participant.CountryId = 1;
var interests = new List<Interest>();
foreach (var interestItem in studentRegisterViewModel.SelectedInterests)
{
var interest = new Interest { Id = interestItem };
interest.Participants.Add(studentRegisterViewModel.Participant);
interests.Add(interest);
studentRegisterViewModel.Participant.Interests.Add(interest);
}
studentRegisterViewModel.Participant.Interests = interests;
_participantService.CreatParticipant(studentRegisterViewModel.Participant);
var user = _userService.GetUser(studentRegisterViewModel.Participant.User.Username);
}
}
studentRegisterViewModel.Gender =
Enum.GetNames(typeof(Gender)).Select(
x => new KeyValuePair<string, string>(x, x.ToString(CultureInfo.InvariantCulture)));
studentRegisterViewModel.Interests = _interestService.GetAllInterests();
return View(studentRegisterViewModel);
}
Participant Repository (DAL)
public class ParticipantRepository : Repository<Participant>, IParticipantRepository
{
public ParticipantRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{
}
}
Participant Service (BLL)
public class ParticipantService : IParticipantService
{
private readonly IParticipantRepository _participantRepository;
private readonly IUnitOfWork _unitOfWork;
public ParticipantService(IParticipantRepository participantRepository, IUnitOfWork unitOfWork)
{
this._participantRepository = participantRepository;
this._unitOfWork = unitOfWork;
}
public void CreatParticipant(Participant participant)
{
_participantRepository.Add(participant);
_unitOfWork.Commit();
}
}
Database Factory
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private STNDataContext _stnDataContext;
public DatabaseFactory()
{
Database.SetInitializer<STNDataContext>(null);
}
public STNDataContext Get()
{
return _stnDataContext ?? (_stnDataContext = new STNDataContext());
}
protected override void DisposeCore()
{
if (_stnDataContext != null)
_stnDataContext.Dispose();
}
}
Unit of Work Class
public class UniOfWork : IUnitOfWork
{
private readonly IDatabaseFactory _databaseFactory;
private STNDataContext _stnDataContext;
public UniOfWork(IDatabaseFactory databaseFactory)
{
this._databaseFactory = databaseFactory;
}
public STNDataContext StnDataContext
{
get { return _stnDataContext ?? (_stnDataContext = _databaseFactory.Get()); }
}
public void Commit()
{
StnDataContext.Commit();
}
}
When I try to Create Participant I get following error.
Cannot insert the value NULL into column 'InterestName', table 'StudyTourNetworkDB.dbo.Interests'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
Ideally as per my thinking it should insert Participant Information in Participants table and Participants Interests in ParticipantsInterests table. But it is trying to insert record in Interests table also which should not happen. Please help me resolve this problem. I may be doing wrong by creating many-to-many association.
Thanks
Note : I could understand the problem as Interests collection does not get added / attach to context but I could not find out how to add Interest collection to the same context with repository pattern and unit of work.
Please provide me the solutions. Thanks in advance
You are correct in that your Interest objects are being re-added, because the copies held in your model are not being tracked by EF and therefore it thinks they are new. Instead, you will need to look up the versions from your repository, and add those instead.
Instead of:
var interests = new List<Interest>();
foreach (var interestItem in studentRegisterViewModel.SelectedInterests)
{
var interest = new Interest { Id = interestItem };
interest.Participants.Add(studentRegisterViewModel.Participant);
interests.Add(interest);
studentRegisterViewModel.Participant.Interests.Add(interest);
}
studentRegisterViewModel.Participant.Interests = interests;
Try something like:
// Look up the actual EF entities which match your selected items. You'll
// probably need to adapt this to make it work
var selectedInterestIds = studentRegisterViewModel.SelectedInterests.Select(i => i.Id);
var interests = _interestService.GetAllInterests().Where(i => selectedInterestIds.Contains(i.Id));
studentRegisterViewModel.Participant.Interests = interests;
Note that with a many-to-many relationship, you don't need to set both sides - in your example you were filling in the Participant field of the Interest entity - this will be set automatically by EF since you're adding it to the Interests property of the Participant.

Need help on EF Code First Fluent Mapping Many To Many

I am developing an ASP.NET MVC4 application with EF Code First. I am having a many-to-many relationship among following classes. I have defined relationship using EF fluent api in my context class. But I am getting an error as it is trying to insert values into one of my master table involved in many-to-many relation. Can anyone help me correct my problem. Thanks in advance and for valuable time.I am using repository pattern and unit of work with Ninject as dependency injection.
Participant Class
public class Participant
{
[Key]
public int Id { get; set; }
[DisplayName("First Name")]
[StringLength(50, ErrorMessage = "First name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in first name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
[StringLength(50, ErrorMessage = "Last name cannot be more than 50 characters")]
[Required(ErrorMessage = "You must fill in last name")]
public string LastName { get; set; }
[Required(ErrorMessage = "You must indicate your full birthday")]
[DisplayName("Birthday")]
[DataType(DataType.DateTime)]
public DateTime BirthDate { get; set; }
[DisplayName("Gender")]
[Required(ErrorMessage = "You must select gender")]
public int Gender { get; set; }
public string Address { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
[DisplayName("Zip code")]
[StringLength(10, ErrorMessage = "Zip code cannot be more than 10 characters")]
public string ZipCode { get; set; }
public string Mobile { get; set; }
public string PhotoUrl { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
}
Interest Class
public class Interest
{
public int Id { get; set; }
public string InterestName { get; set; }
public virtual ICollection<Participant> Participants { get; set; }
}
DataContext
public class STNDataContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Participant> Participants { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Interest> Interests { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<SecurityQuestion> SecurityQuestions { get; set; }
public DbSet<Tour> Tours { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Participant>().
HasMany(p => p.Interests).
WithMany(i => i.Participants).
Map(
m =>
{
m.ToTable("ParticipantInterests");
m.MapLeftKey("ParticipantId");
m.MapRightKey("InterestId");
});
modelBuilder.Entity<User>().HasRequired(u => u.Role);
modelBuilder.Entity<Participant>().HasRequired(p => p.Country);
}
public virtual void Commit()
{
base.SaveChanges();
}
}
Controller Code
public virtual ActionResult Register(StudentRegisterViewModel studentRegisterViewModel)
{
if (ModelState.IsValid)
{
if (_userService.IsUserExists(studentRegisterViewModel.Participant.User) == false)
{
// Attempt to register the user
//WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
//WebSecurity.Login(model.UserName, model.Password);
studentRegisterViewModel.Participant.User.Username = studentRegisterViewModel.Username;
studentRegisterViewModel.Participant.User.Email = studentRegisterViewModel.Email;
studentRegisterViewModel.Participant.User.DateCreated = DateTime.Now;
studentRegisterViewModel.Participant.User.Id = 3;
studentRegisterViewModel.Participant.User.IsApproved = false;
studentRegisterViewModel.Participant.User.RoleId = 2;
studentRegisterViewModel.Participant.CountryId = 1;
var participant = new Participant
{
Id = studentRegisterViewModel.Participant.Id,
FirstName = studentRegisterViewModel.Participant.FirstName,
LastName = studentRegisterViewModel.Participant.LastName,
Interests = new Collection<Interest>()
};
var interests = new List<Interest>();
foreach (var interestItem in studentRegisterViewModel.SelectedInterests)
{
var interest = new Interest { Id = interestItem, Participants = new Collection<Participant>() };
interest.Participants.Add(participant);
interests.Add(interest);
}
studentRegisterViewModel.Participant.Interests = interests;
_participantService.CreatParticipant(studentRegisterViewModel.Participant);
//_userService.CreatUser(studentRegisterViewModel.Participant.User);
//TODO: Need to check if do we need to register the user and get him signed-in. If yes signing in implementation goes here.
var user = _userService.GetUser(studentRegisterViewModel.Participant.User.Username);
//Session["User"] = user;
//FormsAuthentication.SetAuthCookie(user.Username, false);
//Growl("Welcome", "Thanks for registering and welcome to Truck Tracker.");
//return RedirectToAction("Index", "Home");
}
}
//return RedirectToAction("Index", "Home");
// If we got this far, something failed, redisplay form
studentRegisterViewModel.Gender =
Enum.GetNames(typeof(Gender)).Select(
x => new KeyValuePair<string, string>(x, x.ToString(CultureInfo.InvariantCulture)));
studentRegisterViewModel.Interests = _interestService.GetAllInterests();
return View(studentRegisterViewModel);
}
Ideally it should insert Participant into Participants Table and Participant Interests in ParticipantInterests many-to-many table. But it is giving following error
{"Cannot insert the value NULL into column 'InterestName', table 'StudyTourNetworkDB.dbo.Interests'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}
It is trying to insert into Interests table which should not happen.
Participant ParticipantInterests Interests
Id Id Id
FirstName ParticipantId InterestName
LastName InterestId
This is how the tables in the database. The Interest table has a fixed set of records(Study, Job, Other,etc) which get displayed in Interested In dropdown. The registering participant can select multiple interested in options and when he clicks Sign Up button the Participant record will get saved in Participant Table and selected Interests in ParticipantInterests table.
Thanks
I was messing around with the same things the other day. Pain to get it figured out but here is a very basic example of a many to many I got working:
namespace Project.Models
{
public class Affiliate
{
public Affiliate()
{
Merchants = new HashSet<Merchant>();
}
public int Id { get; set; }
public ICollection<Merchant> Merchants { get; set; }
}
}
namespace Project.Models
{
public class Merchant
{
public Merchant()
{
Affiliates = new HashSet<Affiliate>();
}
public int Id { get; set; }
public ICollection<Affiliate> Affiliates{ get; set; }
}
}
and in the DbContext I did this:
modelBuilder.Entity<Merchant>().
HasMany(c => c.Affiliates).
WithMany(p => p.Merchants).
Map(
m =>
{
m.MapLeftKey("MerchantId");
m.MapRightKey("AffiliateId");
m.ToTable("MerchantAffiliates");
});
Update
I am trying to understand what you are looking to accomplish. It seems like this:
A participant can be interested in many things. Multiple participants can be interested in the same thing. If that is the fact I would consider changing the model so a participant has a list of interests, but the interests table, in my opinion, does not need a list of participants. If you wanted to retrieve say all participants with an interest in hiking you could just do something like this:
Context.Participants.ToList().Where(x => x.Interest.Name == "hiking");
Make sense?

Resources