关于c#:首先创建代码,多对多创建代码,并在关联表中添加其他字段

Create code first, many to many, with additional fields in association table

我有这样的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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; }
}

如何配置与Fluent API的关联?还是有更好的方法来创建关联表?


不能用自定义的联接表创建多对多关系。在多对多关系中,EF在内部和隐藏管理联接表。它是一个在模型中没有实体类的表。要使用具有附加属性的此类联接表,您必须实际创建两个一对多关系。它可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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; }
}

例如,如果您现在想查找LastName="smith"成员的所有注释,可以编写这样的查询:

1
2
3
4
var commentsOfMembers = context.Members
    .Where(m => m.LastName =="Smith")
    .SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
    .ToList();

…或者…

1
2
3
4
var commentsOfMembers = context.MemberComments
    .Where(mc => mc.Member.LastName =="Smith")
    .Select(mc => mc.Comment)
    .ToList();

或者,要创建名为"Smith"(我们假定有多个成员)的成员列表及其注释,您可以使用投影:

1
2
3
4
5
6
7
8
var membersWithComments = context.Members
    .Where(m => m.LastName =="Smith")
    .Select(m => new
    {
        Member = m,
        Comments = m.MemberComments.Select(mc => mc.Comment)
    })
    .ToList();

如果要查找MemberId=1的成员的所有注释:

1
2
3
4
var commentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1)
    .Select(mc => mc.Comment)
    .ToList();

现在,您还可以按联接表中的属性进行筛选(这在多对多关系中是不可能的),例如:筛选属性Something中具有99的成员1的所有注释:

1
2
3
4
var filteredCommentsOfMember = context.MemberComments
    .Where(mc => mc.MemberId == 1 && mc.Something == 99)
    .Select(mc => mc.Comment)
    .ToList();

因为懒惰的加载,事情可能变得更容易。如果您有一个已加载的Member,那么您应该能够在不进行显式查询的情况下获得注释:

1
var commentsOfMember = member.MemberComments.Select(mc => mc.Comment);

我想懒惰的加载会在幕后自动获取评论。

编辑

为了好玩,有几个例子更多地说明了如何添加实体和关系,以及如何在此模型中删除它们:

1)创建此成员的一个成员和两个注释:

1
2
3
4
5
6
7
8
9
10
11
12
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)添加成员1的第三条注释:

1
2
3
4
5
6
7
8
9
10
11
12
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)创建新成员并将其与现有注释关联2:

1
2
3
4
5
6
7
8
9
10
11
12
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)在现有成员2和注释3之间创建关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)再次删除此关系:

1
2
3
4
5
6
7
8
9
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)删除成员1及其与注释的所有关系:

1
2
3
4
5
6
7
var member1 = context.Members.Where(m => m.FirstName =="Pete")
    .SingleOrDefault();
if (member1 != null)
{
    context.Members.Remove(member1);
    context.SaveChanges();
}

这也删除了MemberComments中的关系,因为MemberMemberComments之间以及CommentMemberComments之间的一对多关系是按约定级联删除的。这是因为在MemberComment中的MemberIdCommentId被检测为MemberComment导航属性的外键属性,并且由于fk属性的类型是不可为空的int类型,因此需要这种关系,最终导致级联删除设置。我认为这个模型是有道理的。


斯劳玛回答得很好。

我将使用fluent api映射发布代码来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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; }
}

在您的DbContext派生类上,您可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)
    }
}

它与公认的答案具有相同的效果,采用不同的方法,效果既不好也不差。

编辑:I've changed createddate from bool to datetime.

编辑2:由于缺乏时间,我从一个正在开发的应用程序中放置了一个示例,以确保这一点有效。


@Esteban,你提供的代码是正确的,谢谢,但不完整,我已经测试过了。"useremail"类中缺少属性:

1
2
    public UserTest UserTest { get; set; }
    public EmailTest EmailTest { get; set; }

如果有人感兴趣,我会发布我测试过的代码。当做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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

我想提出一个解决方案,在这个方案中可以实现多对多配置的两种风格。

"catch"是我们需要创建一个以join表为目标的视图,因为ef验证一个模式的表在每个EntitySet中最多可以映射一次。

这个答案增加了之前答案中已经说过的内容,并且没有覆盖任何这些方法,它建立在它们之上。

模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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; }
}

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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);
    }
}

语境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
     }
}

从Saluma(@Saluma)的回答

If you now want to find all comments of members with LastName =
"Smith" for example you can write a query like this:

这仍然有效…

1
2
3
4
var commentsOfMembers = context.Members
    .Where(m => m.LastName =="Smith")
    .SelectMany(m => m.MemberComments.Select(mc => mc.Comment))
    .ToList();

…但现在也可能是…

1
2
3
4
var commentsOfMembers = context.Members
    .Where(m => m.LastName =="Smith")
    .SelectMany(m => m.Comments)
    .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:

这仍然有效…

1
2
3
4
5
6
7
8
var membersWithComments = context.Members
    .Where(m => m.LastName =="Smith")
    .Select(m => new
    {
        Member = m,
        Comments = m.MemberComments.Select(mc => mc.Comment)
    })
    .ToList();

…但现在也可能是…

1
2
3
4
5
6
7
8
var membersWithComments = context.Members
    .Where(m => m.LastName =="Smith")
    .Select(m => new
    {
        Member = m,
        m.Comments
    })
        .ToList();

如果要从成员中删除注释

1
2
3
4
var comment = ... // assume comment from member John Smith
var member = ... // assume member John Smith

member.Comments.Remove(comment);

如果你想删除会员的评论

1
2
3
var member = context.Members
    .Where(m => m.FirstName =="John", m.LastName =="Smith")
    .Include(m => m.Comments);

这一切都像是语法上的甜头,但是如果你愿意进行额外的配置,它确实会给你带来一些好处。无论哪种方法,你似乎都能从这两种方法中得到最好的。


解决此错误的一种方法是将ForeignKey属性放在要用作外键的属性的顶部,然后添加导航属性。

注意:在ForeignKey属性中,在括号和双引号之间,用这种方式放置引用的类的名称。

enter image description here


tldr;(与ef6/vs2012u5中的ef编辑器bug半相关)如果从db生成模型,并且看不到属性m:m表:删除两个相关表->save.edmx->generate/add from database->save。

对于那些来这里想知道如何在ef.edmx文件中显示与属性列的多对多关系的人(因为它目前不会显示并被视为一组导航属性),您从您的数据库表(或者我相信是MS Lingo中的第一个数据库)生成了这些类。

删除.edmx中有问题的两个表(以op示例、成员和注释为例),然后通过"从数据库生成模型"再次添加它们。(即,不要试图让Visual Studio更新它们-删除、保存、添加、保存)

然后它将根据这里的建议创建第三个表。

这与最初添加纯多对多关系以及稍后在数据库中设计属性的情况有关。

这个线索/谷歌搜索并没有立即弄清楚。所以只要把它放在外面,因为这是谷歌的链接1,寻找问题,但首先来自数据库方面。