I created a database, then applied dataase-first. Then it automatically imported the database to VS. Please tell me, when database-first automatically indicates relationship? Probably not, my data is not being imported. Could you tell me how to establish connections correctly? I read about the fluent api and about the fact that you can specify keys and properties directly in the table classes (And when is it better to do through fluent, and when to specify directly?)
My 1st table
namespace WcfRestFullService.Model
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Web;
[DataContract]
public partial class customer
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public customer()
{
this.dishesrankings = new HashSet<dishesranking>();
this.orders = new HashSet<order>();
}
[DataMember]
public int Id_Cus { get; set; }
[DataMember]
public string FirstName_Cus { get; set; }
[DataMember]
public string LastName_Cus { get; set; }
[DataMember]
public int PhoneNum_Cus { get; set; }
[DataMember]
public string Email_Cus { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<dishesranking> dishesrankings { get; set; }
public virtual customerpreference customerpreference { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<order> orders { get; set; }
}
}
My 2nd table
namespace WcfRestFullService.Model
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Runtime.Serialization;
using System.Web;
[DataContract]
public partial class customerpreference
{
[DataMember]
public int Id_Cus { get; set; }
[DataMember]
public int Id_Res { get; set; }
[DataMember]
public string Name_Dis { get; set; }
[DataMember]
public int Id_Type { get; set; }
public virtual customer customer { get; set; }
public virtual order order { get; set; }
public virtual type_dishes type_dishes { get; set; }
}
}
MySQLEntities
namespace WcfRestFullService.Model
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class MySQLEntities : DbContext
{
public MySQLEntities()
: base("name=MySQLEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<customer>()
.HasMany(c => c.customerpreference)
.WithOptional(o => o.Customer);
//throw new UnintentionalCodeFirstException();//here problem
}
public virtual DbSet<customer> customers { get; set; }
public virtual DbSet<customerpreference> customerpreferences { get; set; }
public virtual DbSet<dish> dishes { get; set; }
public virtual DbSet<dishesranking> dishesrankings { get; set; }
public virtual DbSet<ingridient> ingridients { get; set; }
public virtual DbSet<order> orders { get; set; }
public virtual DbSet<restaraunt> restaraunts { get; set; }
public virtual DbSet<type_dishes> type_dishes { get; set; }
public object Parameters { get; internal set; }
}
}
Here I create data(Id_Cus) but it doesn't import in 2nd table
public void InsertCustomer(customer customerDataContract)
{
//MySQLEntities Cust = new MySQLEntities();
customer cust = new customer();
{
cust.Id_Cus = Convert.ToInt32(customerDataContract.Id_Cus);
cust.FirstName_Cus = customerDataContract.FirstName_Cus;
cust.LastName_Cus = customerDataContract.LastName_Cus;
cust.PhoneNum_Cus = Convert.ToInt32(customerDataContract.PhoneNum_Cus);
cust.Email_Cus = customerDataContract.Email_Cus;
};
dc.customers.Add(cust);
customerpreference custPref = new customerpreference()
{
Id_Cus = customerDataContract.Id_Cus,
Id_Res = 0, // some value
Name_Dis = null, // some value
Id_Type = 0 // some value
};
dc.customerpreferences.Add(custPref);
dc.SaveChanges();
int k = Convert.ToInt32(cust.Id_Cus);
customer custFromDb =(from n in dc.customers
where n.Id_Cus == k
select n).Include(c => c.customerpreference).First();
}
perhaps problem in
cust = (from n in dc.customers
where n.Id_Cus == k
select n).Include(c =>c.customerpreference).ToList().First();
dc.customers.Add(cust);
dc.SaveChanges();
Yes it does automatically model foreign keys. You can see that it has done so in your model because there are navigation properties such as dishesrankings in your Customer class.
We had a database first project. We would update it by changing the database using dbup and then updating the model from the database. This way you ensure consistency between the model and the database.
DbUp: https://dbup.github.io/
DbUp is a tool to run scripts to make changes to the database that allows versioning and rollback and it is very useful if you're using database first.
I'm trying to select the users with their modules using the composite key. I can select on MySQL with SQL query but I'm new to entity framework and I'm having troubles figuring out. I appreciate any help that I can get.
public class module
{
public int moduleid { get; set; }
public string modulename { get; set; }
[ForeignKey("module_moduleid")]
public ICollection<session> sessions { get; set; }
public ICollection<user_has_module> usermodule { get; set; }
}
public class user_has_module
{
public String userid { get; set; }
public int moduleid { get; set; }
public user user { get; set; }
public module module { get; set; }
}
public class user
{
[Key]
public string userid { get; set; }
public string userpw { get; set; }
public string fullname { get; set; }
public ICollection<user_has_module> usermodule { get; set; }
}
This is my database context class.
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
public DbSet<user> user { get; set; }
public DbSet<module> module { get; set; }
public DbSet<user_has_module> user_has_module { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<user_has_module>()
.HasKey(x => new { x.userid, x.moduleid });
modelBuilder.Entity<user_has_module>()
.HasOne(x => x.user)
.WithMany(y => y.usermodule)
.HasForeignKey(y => y.userid);
modelBuilder.Entity<user_has_module>()
.HasOne(x => x.module)
.WithMany(y => y.usermodule)
.HasForeignKey(y => y.moduleid);
}
}
This is the query I'm trying to do in entity framework core.
From user
INNER JOIN user_has_module
ON user.userid = user_has_module.user_userid
INNER JOIN module
ON user_has_module.module_moduleid = module.moduleid;
To retrieve data from three tables using a composite key, you can be inspired by the following example :
var query = from o in db.Orders
from p in db.Products
join d in db.OrderDetails
on new {o.OrderID, p.ProductID} equals new {d.OrderID, d.ProductID} into details
from d in details
select new {o.OrderID, p.ProductID, d.UnitPrice};
Hope it helps !
I have a probably simple question, I am trying to create many to many relationships using entity framework and fluent api, and my problem is that when i try to do any query or view a object in debug it has always 0 items.
I am using junction table that looks like:
So relations exists, to be sure ive checked:
select candidate.firstname, skillset.name
from candidate
join candidate_skillset on candidate.id = candidate_skillset.candidate_id
join skillset on candidate_skillset.skillset_id = skillset.id
and joined results are displayed.
Now my context looks like:
public class CatalogContexct : DbContext
{
public DbSet<Candidate> Candidates { get; set; }
public DbSet<SkillSet> SkillSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Candidate>().HasMany(t => t.SkillSets).WithMany(t => t.Candidates)
.Map(m =>
{
m.ToTable("candidate_skillset");
m.MapLeftKey("candidate_id");
m.MapRightKey("skillset_id");
});
modelBuilder.Entity<SkillSet>().ToTable("skillset");
modelBuilder.Entity<Candidate>().ToTable("candidate");
}
}
My left side model candidates:
[Table("candidate")]
public class Candidate
{
public Candidate()
{
this.SkillSets = new HashSet<SkillSet>();
}
[Key]
public int id { get; set; }
[Column("firstname")]
public string Firstname { get; set; }
public int? commendation_id { get; set; }
[ForeignKey("commendation_id")]
public Commendation commendation { get; set; }
public ICollection<SkillSet> SkillSets { get; set; }
}
And my rightside model skillset:
[Table("skillset")]
public class SkillSet : SimpleDictionary
{
public SkillSet()
{
this.Candidates = new HashSet<Candidate>();
}
public virtual ICollection<Candidate> Candidates { get; set; }
}
and that model has a parent class:
public class SimpleDictionary
{
[Key]
public int id { get; set; }
[Column("name")]
public string Name { get; set; }
}
So all should work but when I do for example:
var ca = this._catalog.Candidates
.Include("SkillSets").Include("commendation").
FirstOrDefault(x => x.SkillSets.Any());
Result is null, also when I view object on debug collection of property skillset allays has 0 elements, any idea what could be wrong with it?
I tried this with same structure mentioned here in you question and tried locally . And I am able to get the data with this code . Please try this and let me know if this helps . I just omitted commendation table for simplicity .
var context = new SampleDbContext();
var candidates = context.Candidates
.Include("SkillSets").ToList();
foreach (var candidate in candidates)
{
foreach (var sk in candidate.SkillSets.Where( s1 => s1.Candidates.Count(c=>c.id == candidate.id)>0 ))
{
Console.WriteLine( string.Format(#" Name : {0} Skill :{1}",candidate.Firstname ,sk.Name ) );
}
}
Below is my DbContext and Other Entity Classes
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
public class SampleDbContext : DbContext
{
public SampleDbContext()
: base("name=SampleDBConnection")
{
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Candidate> Candidates { get; set; }
public DbSet<SkillSet> SkillSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Candidate>().HasMany(t => t.SkillSets).WithMany(t => t.Candidates)
.Map(m =>
{
m.ToTable("candidate_skillset");
m.MapLeftKey("candidate_id");
m.MapRightKey("skillset_id");
});
modelBuilder.Entity<SkillSet>().ToTable("skillset");
modelBuilder.Entity<Candidate>().ToTable("candidate");
}
}
[Table("candidate")]
public class Candidate
{
public Candidate()
{
this.SkillSets = new HashSet<SkillSet>();
}
[Key]
public int id { get; set; }
[Column("firstname")]
public string Firstname { get; set; }
public int? commendation_id { get; set; }
//[ForeignKey("commendation_id")]
//public Commendation commendation { get; set; }
public ICollection<SkillSet> SkillSets { get; set; }
}
public class SimpleDictionary
{
[Key]
public int id { get; set; }
[Column("name")]
public string Name { get; set; }
}
[Table("skillset")]
public class SkillSet : SimpleDictionary
{
public SkillSet()
{
this.Candidates = new HashSet<Candidate>();
}
public virtual ICollection<Candidate> Candidates { get; set; }
}
}
The output of the query you mentioned and the result of my code both matched I hope this is that you wanted .
I have this scenario:
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }
public virtual ICollection<Member> Members { get; set; }
}
public class MemberComment
{
public int MemberID { get; set; }
public int CommentID { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
}
How do I configure my association with fluent API? Or is there a better way to create the association table?
It's not possible to create a many-to-many relationship with a customized join table. In a many-to-many relationship EF manages the join table internally and hidden. It's a table without an Entity class in your model. To work with such a join table with additional properties you will have to create actually two one-to-many relationships. It could look like this:
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<MemberComment> MemberComments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }
public virtual ICollection<MemberComment> MemberComments { get; set; }
}
public class MemberComment
{
[Key, Column(Order = 0)]
public int MemberID { get; set; }
[Key, Column(Order = 1)]
public int CommentID { get; set; }
public virtual Member Member { get; set; }
public virtual Comment Comment { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
}
If you now want to find all comments of members with LastName = "Smith" for example you can write a query like this:
var commentsOfMembers = context.Members
.Where(m => m.LastName == "Smith")
.SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
.ToList();
... or ...
var commentsOfMembers = context.MemberComments
.Where(mc => mc.Member.LastName == "Smith")
.Select(mc => mc.Comment)
.ToList();
Or to create a list of members with name "Smith" (we assume there is more than one) along with their comments you can use a projection:
var membersWithComments = context.Members
.Where(m => m.LastName == "Smith")
.Select(m => new
{
Member = m,
Comments = m.MemberComments.Select(mc => mc.Comment)
})
.ToList();
If you want to find all comments of a member with MemberId = 1:
var commentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1)
.Select(mc => mc.Comment)
.ToList();
Now you can also filter by the properties in your join table (which would not be possible in a many-to-many relationship), for example: Filter all comments of member 1 which have a 99 in property Something:
var filteredCommentsOfMember = context.MemberComments
.Where(mc => mc.MemberId == 1 && mc.Something == 99)
.Select(mc => mc.Comment)
.ToList();
Because of lazy loading things might become easier. If you have a loaded Member you should be able to get the comments without an explicit query:
var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);
I guess that lazy loading will fetch the comments automatically behind the scenes.
Edit
Just for fun a few examples more how to add entities and relationships and how to delete them in this model:
1) Create one member and two comments of this member:
var member1 = new Member { FirstName = "Pete" };
var comment1 = new Comment { Message = "Good morning!" };
var comment2 = new Comment { Message = "Good evening!" };
var memberComment1 = new MemberComment { Member = member1, Comment = comment1,
Something = 101 };
var memberComment2 = new MemberComment { Member = member1, Comment = comment2,
Something = 102 };
context.MemberComments.Add(memberComment1); // will also add member1 and comment1
context.MemberComments.Add(memberComment2); // will also add comment2
context.SaveChanges();
2) Add a third comment of member1:
var member1 = context.Members.Where(m => m.FirstName == "Pete")
.SingleOrDefault();
if (member1 != null)
{
var comment3 = new Comment { Message = "Good night!" };
var memberComment3 = new MemberComment { Member = member1,
Comment = comment3,
Something = 103 };
context.MemberComments.Add(memberComment3); // will also add comment3
context.SaveChanges();
}
3) Create new member and relate it to the existing comment2:
var comment2 = context.Comments.Where(c => c.Message == "Good evening!")
.SingleOrDefault();
if (comment2 != null)
{
var member2 = new Member { FirstName = "Paul" };
var memberComment4 = new MemberComment { Member = member2,
Comment = comment2,
Something = 201 };
context.MemberComments.Add(memberComment4);
context.SaveChanges();
}
4) Create relationship between existing member2 and comment3:
var member2 = context.Members.Where(m => m.FirstName == "Paul")
.SingleOrDefault();
var comment3 = context.Comments.Where(c => c.Message == "Good night!")
.SingleOrDefault();
if (member2 != null && comment3 != null)
{
var memberComment5 = new MemberComment { Member = member2,
Comment = comment3,
Something = 202 };
context.MemberComments.Add(memberComment5);
context.SaveChanges();
}
5) Delete this relationship again:
var memberComment5 = context.MemberComments
.Where(mc => mc.Member.FirstName == "Paul"
&& mc.Comment.Message == "Good night!")
.SingleOrDefault();
if (memberComment5 != null)
{
context.MemberComments.Remove(memberComment5);
context.SaveChanges();
}
6) Delete member1 and all its relationships to the comments:
var member1 = context.Members.Where(m => m.FirstName == "Pete")
.SingleOrDefault();
if (member1 != null)
{
context.Members.Remove(member1);
context.SaveChanges();
}
This deletes the relationships in MemberComments too because the one-to-many relationships between Member and MemberComments and between Comment and MemberComments are setup with cascading delete by convention. And this is the case because MemberId and CommentId in MemberComment are detected as foreign key properties for the Member and Comment navigation properties and since the FK properties are of type non-nullable int the relationship is required which finally causes the cascading-delete-setup. Makes sense in this model, I think.
I'll just post the code to do this using the fluent API mapping.
public class User {
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public ICollection<UserEmail> UserEmails { get; set; }
}
public class Email {
public int EmailID { get; set; }
public string Address { get; set; }
public ICollection<UserEmail> UserEmails { get; set; }
}
public class UserEmail {
public int UserID { get; set; }
public int EmailID { get; set; }
public bool IsPrimary { get; set; }
}
On your DbContext derived class you could do this:
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder builder) {
// Primary keys
builder.Entity<User>().HasKey(q => q.UserID);
builder.Entity<Email>().HasKey(q => q.EmailID);
builder.Entity<UserEmail>().HasKey(q =>
new {
q.UserID, q.EmailID
});
// Relationships
builder.Entity<UserEmail>()
.HasRequired(t => t.Email)
.WithMany(t => t.UserEmails)
.HasForeignKey(t => t.EmailID)
builder.Entity<UserEmail>()
.HasRequired(t => t.User)
.WithMany(t => t.UserEmails)
.HasForeignKey(t => t.UserID)
}
}
It has the same effect as the accepted answer, with a different approach, which is no better nor worse.
The code provided by this answer is right, but incomplete, I've tested it. There are missing properties in "UserEmail" class:
public UserTest UserTest { get; set; }
public EmailTest EmailTest { get; set; }
I post the code I've tested if someone is interested.
Regards
using System.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
#region example2
public class UserTest
{
public int UserTestID { get; set; }
public string UserTestname { get; set; }
public string Password { get; set; }
public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }
public static void DoSomeTest(ApplicationDbContext context)
{
for (int i = 0; i < 5; i++)
{
var user = context.UserTest.Add(new UserTest() { UserTestname = "Test" + i });
var address = context.EmailTest.Add(new EmailTest() { Address = "address#" + i });
}
context.SaveChanges();
foreach (var user in context.UserTest.Include(t => t.UserTestEmailTests))
{
foreach (var address in context.EmailTest)
{
user.UserTestEmailTests.Add(new UserTestEmailTest() { UserTest = user, EmailTest = address, n1 = user.UserTestID, n2 = address.EmailTestID });
}
}
context.SaveChanges();
}
}
public class EmailTest
{
public int EmailTestID { get; set; }
public string Address { get; set; }
public ICollection<UserTestEmailTest> UserTestEmailTests { get; set; }
}
public class UserTestEmailTest
{
public int UserTestID { get; set; }
public UserTest UserTest { get; set; }
public int EmailTestID { get; set; }
public EmailTest EmailTest { get; set; }
public int n1 { get; set; }
public int n2 { get; set; }
//Call this code from ApplicationDbContext.ConfigureMapping
//and add this lines as well:
//public System.Data.Entity.DbSet<yournamespace.UserTest> UserTest { get; set; }
//public System.Data.Entity.DbSet<yournamespace.EmailTest> EmailTest { get; set; }
internal static void RelateFluent(System.Data.Entity.DbModelBuilder builder)
{
// Primary keys
builder.Entity<UserTest>().HasKey(q => q.UserTestID);
builder.Entity<EmailTest>().HasKey(q => q.EmailTestID);
builder.Entity<UserTestEmailTest>().HasKey(q =>
new
{
q.UserTestID,
q.EmailTestID
});
// Relationships
builder.Entity<UserTestEmailTest>()
.HasRequired(t => t.EmailTest)
.WithMany(t => t.UserTestEmailTests)
.HasForeignKey(t => t.EmailTestID);
builder.Entity<UserTestEmailTest>()
.HasRequired(t => t.UserTest)
.WithMany(t => t.UserTestEmailTests)
.HasForeignKey(t => t.UserTestID);
}
}
#endregion
I want to propose a solution where both flavors of a many-to-many configuration can be achieved.
The "catch" is we need to create a view that targets the Join Table, since EF validates that a schema's table may be mapped at most once per EntitySet.
This answer adds to what's already been said in previous answers and doesn't override any of those approaches, it builds upon them.
The model:
public class Member
{
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<MemberCommentView> MemberComments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public string Message { get; set; }
public virtual ICollection<Member> Members { get; set; }
public virtual ICollection<MemberCommentView> MemberComments { get; set; }
}
public class MemberCommentView
{
public int MemberID { get; set; }
public int CommentID { get; set; }
public int Something { get; set; }
public string SomethingElse { get; set; }
public virtual Member Member { get; set; }
public virtual Comment Comment { get; set; }
}
The configuration:
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
public class MemberConfiguration : EntityTypeConfiguration<Member>
{
public MemberConfiguration()
{
HasKey(x => x.MemberID);
Property(x => x.MemberID).HasColumnType("int").IsRequired();
Property(x => x.FirstName).HasColumnType("varchar(512)");
Property(x => x.LastName).HasColumnType("varchar(512)")
// configure many-to-many through internal EF EntitySet
HasMany(s => s.Comments)
.WithMany(c => c.Members)
.Map(cs =>
{
cs.ToTable("MemberComment");
cs.MapLeftKey("MemberID");
cs.MapRightKey("CommentID");
});
}
}
public class CommentConfiguration : EntityTypeConfiguration<Comment>
{
public CommentConfiguration()
{
HasKey(x => x.CommentID);
Property(x => x.CommentID).HasColumnType("int").IsRequired();
Property(x => x.Message).HasColumnType("varchar(max)");
}
}
public class MemberCommentViewConfiguration : EntityTypeConfiguration<MemberCommentView>
{
public MemberCommentViewConfiguration()
{
ToTable("MemberCommentView");
HasKey(x => new { x.MemberID, x.CommentID });
Property(x => x.MemberID).HasColumnType("int").IsRequired();
Property(x => x.CommentID).HasColumnType("int").IsRequired();
Property(x => x.Something).HasColumnType("int");
Property(x => x.SomethingElse).HasColumnType("varchar(max)");
// configure one-to-many targeting the Join Table view
// making all of its properties available
HasRequired(a => a.Member).WithMany(b => b.MemberComments);
HasRequired(a => a.Comment).WithMany(b => b.MemberComments);
}
}
The context:
using System.Data.Entity;
public class MyContext : DbContext
{
public DbSet<Member> Members { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<MemberCommentView> MemberComments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new MemberConfiguration());
modelBuilder.Configurations.Add(new CommentConfiguration());
modelBuilder.Configurations.Add(new MemberCommentViewConfiguration());
OnModelCreatingPartial(modelBuilder);
}
}
From Saluma's (#Saluma) answer
If you now want to find all comments of members with LastName =
"Smith" for example you can write a query like this:
This still works...
var commentsOfMembers = context.Members
.Where(m => m.LastName == "Smith")
.SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
.ToList();
...but could now also be...
var commentsOfMembers = context.Members
.Where(m => m.LastName == "Smith")
.SelectMany(m => m.Comments)
.ToList();
Or to create a list of members with the name "Smith" (we assume there is
more than one) along with their comments you can use a projection:
This still works...
var membersWithComments = context.Members
.Where(m => m.LastName == "Smith")
.Select(m => new
{
Member = m,
Comments = m.MemberComments.Select(mc => mc.Comment)
})
.ToList();
...but could now also be...
var membersWithComments = context.Members
.Where(m => m.LastName == "Smith")
.Select(m => new
{
Member = m,
m.Comments
})
.ToList();
If you want to remove a comment from a member
var comment = ... // assume comment from member John Smith
var member = ... // assume member John Smith
member.Comments.Remove(comment);
If you want to Include() a member's comments
var member = context.Members
.Where(m => m.FirstName == "John", m.LastName == "Smith")
.Include(m => m.Comments);
This all feels like syntactic sugar, however, it does get you a few perks if you're willing to go through the additional configuration. Either way, you seem to be able to get the best of both approaches.
I've come back here a couple times now, but it seems that EF Core has done a few updates in the past decade, so here's where I'm at currently with setting up many-to-many with custom join entity:
public class MemberModel
{
public int MemberId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<CommentModel> Comments { get; set; }
}
public class CommentModel
{
public int CommentId { get; set; }
public string Message { get; set; }
public ICollection<MemberModel> Members { get; set; }
}
public class MemberCommentModel
{
public int Something { get; set; }
public string SomethingElse { get; set; }
public int MembersId { get; set; }
[ForeignKey("MembersId")]
public MemberModel Member { get; set; }
public int CommentsId { get; set; }
[ForeignKey("CommentsId")]
public CommentModel Comment { get; set; }
}
Then in your OnModelCreating:
//Allows access directly from Comments or Members entities to the other
builder.Entity<MemberModel>()
.HasMany(x => x.Comments)
.WithMany(x => x.Members)
.UsingEntity<MemberCommentModel>();
//Defines the actual relationships for the middle table
builder.Entity<MemberCommentModel>()
.HasOne(x => x.Comment)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<MemberCommentModel>()
.HasOne(x => x.Member)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
TLDR; (semi-related to an EF editor bug in EF6/VS2012U5) if you generate the model from DB and you cannot see the attributed m:m table: Delete the two related tables -> Save .edmx -> Generate/add from database -> Save.
For those who came here wondering how to get a many-to-many relationship with attribute columns to show in the EF .edmx file (as it would currently not show and be treated as a set of navigational properties), AND you generated these classes from your database table (or database-first in MS lingo, I believe.)
Delete the 2 tables in question (to take the OP example, Member and Comment) in your .edmx and add them again through 'Generate model from database'. (i.e. do not attempt to let Visual Studio update them - delete, save, add, save)
It will then create a 3rd table in line with what is suggested here.
This is relevant in cases where a pure many-to-many relationship is added at first, and the attributes are designed in the DB later.
This was not immediately clear from this thread/Googling. So just putting it out there as this is link #1 on Google looking for the issue but coming from the DB side first.
One way to solve this error is to put the ForeignKey attribute on top of the property you want as a foreign key and add the navigation property.
Note: In the ForeignKey attribute, between parentheses and double quotes, place the name of the class referred to in this way.
I've got the following model and I want ShiftRequest and MissionRequest to have a single table in the DB.
public class RequestBase
{
public int Id { get; set; }
public DateTime? RequestDate { get; set; }
public int UserId { get; set; }
public virtual ICollection<Notification> Notifications { get; set; }
}
public class ShiftRequest : RequestBase
{
public virtual Column Column { get; set; }
}
public class MissionRequest : RequestBase
{
public virtual Mission Mission { get; set; }
}
I've tried to do it in the override void OnModelCreating(ModelBuilder modelBuilder) method but only one RequestBases table is created:
modelBuilder.Entity<ShiftRequest>().MapSingleType().ToTable("dbo.ShiftRequests");
modelBuilder.Entity<MissionRequest>().MapSingleType().ToTable("dbo.MissionRequest");
What am I doing wrong?
EDIT
Column and Mission are also entities in my model, is that acceptable?
Check the section about TPH in this article. If Mission and Column are complex types you will also find there how to map them. Generally you have to use MapHiearchy and Case methods instead of MapSingleType.
Edit:
Here is the example:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace EFTest
{
public class RequestBase
{
public int Id { get; set; }
public DateTime? RequestedDate { get; set; }
public int UserId { get; set; }
}
public class Mission
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<MissionRequest> MissionRequests { get; set; }
}
public class Column
{
public string Name { get; set; }
}
public class MissionRequest : RequestBase
{
public virtual Mission Mission { get; set; }
}
public class ShiftRequest : RequestBase
{
public Column Column { get; set; }
}
public class TestContext : DbContext
{
public DbSet<RequestBase> Requests { get; set; }
public DbSet<Mission> Missions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ContainerName = "EFTest";
modelBuilder.IncludeMetadataInDatabase = false;
// Example of complex type mapping. First you have to define
// complex type. Than you can access type properties in
// MapHiearchy.
var columnType = modelBuilder.ComplexType<Column>();
columnType.Property(c => c.Name).HasMaxLength(50);
modelBuilder.Entity<Mission>()
.Property(m => m.Id)
.IsIdentity();
modelBuilder.Entity<Mission>()
.HasKey(m => m.Id)
.MapSingleType(m => new { m.Id, m.Name })
.ToTable("dbo.Missions");
modelBuilder.Entity<RequestBase>()
.Property(r => r.Id)
.IsIdentity();
// You map multiple entities to single table. You have to
// add some discriminator to differ entity type in the table.
modelBuilder.Entity<RequestBase>()
.HasKey(r => r.Id)
.MapHierarchy()
.Case<RequestBase>(r => new { r.Id, r.RequestedDate, r.UserId, Discriminator = 0 })
.Case<MissionRequest>(m => new { MissionId = m.Mission.Id, Discriminator = 1 })
.Case<ShiftRequest>(s => new { ColumnName = s.Column.Name, Discriminator = 2 })
.ToTable("dbo.Requests");
}
}
}
Edit 2:
I updated example. Now Mission is entity instead of complex type.