实现聊天功能时违反了多重性约束错误。

3

我正在实现与此类似的ASP.NET聊天功能。以下是模型说明:模型如下

我的类是:用户(User)、对话(Conversation)和消息(Message):

public class User
{        
    public Guid Id { get; set; }
    public string Name { get; set; }
    public bool Active { get; set; }
    public string UserName { get; set; }       
    //keys       
    public ICollection<Conversation> Conversations { get; set; }
}
public class Conversation
{        
    public Guid ID { get; set; }
    public ICollection<Message> Messages { get; set; }
    public User RecipientUser { get; set; }
    public Guid SenderUserId { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime ModifiedDate { get; set; }
}

最后,
public class Message
{

    public int ID { get; set; }
    public string Text { get; set; }
    public bool IsDelivered { get; set; }
    public bool IsSeen { get; set; }
    public Guid SenderUserId { get; set; }
    public Conversation Conversation { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime ModifiedDate { get; set; }
}

我正在使用使用流畅Api的EntityTypeConfiguration,它们是:

public class UserConfig : EntityTypeConfiguration<User>
{
    public UserConfig()
    {
        HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser).HasForeignKey(x => x.SenderUserId);
    }
}
 public class ConversationConfig : EntityTypeConfiguration<Conversation>
{
    public ConversationConfig()
    {
        HasKey(x => x.ID);
        HasRequired(x => x.RecipientUser).WithMany(x => x.Conversations);
        HasMany(x => x.Messages).WithRequired(x => x.Conversation).HasForeignKey(x => x.SenderUserId);           
    }
}

但我遇到了以下错误: 多重性约束冲突。关系“DataAcessLayer.Conversation_RecipientUser”的角色“Conversation_RecipientUser_Target”具有多重性1或0..1。

如果有人能帮助我,非常感谢!!!


你的代码看起来还不错?你能否写一个示例来展示你如何使用它们或者如何连接数据? - Bassam Alugili
var conversation = new Conversation(); conversation.Messages.Add(message); conversation.RecipientUser = user; currentUser.Conversations.Add(conversation); 这是我连接数据的方式。 - Ajay Kumar
2个回答

1
我在使用你的模型时没有遇到异常,但这并不重要,因为你必须进行一些更改。
首先,在 UserConfig 中的这个配置...
HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser)
              .HasForeignKey(x => x.SenderUserId);

...对我来说没有意义。这意味着对话属于接收者,而不是发送者,这本身有些出乎意料。但是外键的名称是SenderUserId。如果您将对象保存到此模型中,SenderUserId将获得接收者的ID的值!

其次,假设前面的观点是错误的,您通过定义...使其变得比必要的更难。

public User RecipientUser { get; set; }
public Guid SenderUserId { get; set; }

这意味着您只能通过显式连接SenderUserId来从Conversation导航到发件人User。反过来,您只能通过设置RecipientUser来设置收件人,而不能仅仅设置一个外键值。
第三,您不应该在Message中拥有这个SenderUserId。相反,应该使用ConversationId。您可以通过所属的Conversation找到Message的发送者。
总之,我将您的模型更改为最简化版本,并使用int作为ID,只是因为它更容易阅读。
public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
    public ICollection<Conversation> Conversations { get; set; }
}

public class Conversation
{
    public int ID { get; set; }
    public int SenderUserID { get; set; }
    public User SenderUser { get; set;}
    public int RecipientUserID { get; set; }
    public User RecipientUser { get; set; }
    public ICollection<Message> Messages { get; set; }
}

public class Message
{
    public int ID { get; set; }
    public string Text { get; set; }
    public int ConversationID { get; set; }
    public Conversation Conversation { get; set; }
}

而且唯一的配置:

public class ConversationConfig : EntityTypeConfiguration<Conversation>
{
    public ConversationConfig()
    {
        HasKey(c => c.ID);
        HasRequired(c => c.SenderUser).WithMany(u => u.Conversations)
                          .HasForeignKey(c => c.SenderUserID).WillCascadeOnDelete(true);
        HasRequired(c => c.RecipientUser).WithMany()
                          .HasForeignKey(c => c.RecipientUserID).WillCascadeOnDelete(false);
        HasMany(c => c.Messages).WithRequired(x => x.Conversation)
                      .HasForeignKey(x => x.ConversationID);
    }
}

因为这会导致 SQL-Server 错误:多个级联路径,所以其中一个外键没有级联删除。

一个简单的测试:

using (DbContext db = new DbContext(connectionString))
{
    var send = new User { Name = "Sender" };
    db.Set<User>().Add(send);

    var rec = new User { Name = "Recipient" };
    var messages = new[] { new Message { Text = "a" }, new Message { Text = "b" } };
    var conv = new Conversation { SenderUser = send, RecipientUser = rec, Messages = messages };

    db.Set<User>().Add(rec);
    db.Set<Conversation>().Add(conv);

    db.SaveChanges();
}

结果:

用户:

ID    Name
1     Recipient
2     Sender

对话:

ID    RecipientUserID    SenderUserID
11    1                  2

消息:

ID    Text    ConversationID
21    a       11
22    b       11

谢谢您,先生。它的表现非常好。此外,我不得不在Conversation类中添加public User SenderUser {get;set;}。非常感谢。 - Ajay Kumar

0

给定的对话具有一个名为SenderUserId的用户外键列。

    // Here you set the user key for the old user! 
    conversation.RecipientUser = user;

    // Here you are trying to change Conversation.SenderUserId from other user (currentUser)
    currentUser.Conversations.Add(conversation);

正如您所看到的,将用户添加到集合中的方式与您分配给Conversation 1..n的方式不同,这样做可能无法正确工作。

EF试图通过异常告诉您,您正在尝试添加/更改超过一个实体以满足一对零或一关系,错误消息有点模糊,我认为错误消息应该是“一对多导航属性应该使用相同的实体”

解决方案1)

    //  conversation.RecipientUser = user; remove this line why you need it?! 

解决方案2)

在对话一中创建一个新属性,一个用于CurrentUser,另一个用于User。


public class User
{
  public User()
  {
   Conversations = new List<Conversation>();
   CurrentUserConversations = new List<Conversation>();
   }

   public Guid Id { get; set; }
   public string Name { get; set; }
   public bool Active { get; set; }
   public string UserName { get; set; }
   public ICollection<Conversation> Conversations { get; set; }
   public ICollection<Conversation> CurrentUserConversations { get; set; }
}

public class Conversation
{
 public Conversation()
 {
  Messages = new List<Message>();
 }
 public Guid ID { get; set; }
 public User RecipientUser { get; set; }
 public Guid SenderUserId { get; set; }
 public User CurrentUser { get; set; }
 public Guid? CurrentUserId { get; set; }
 public ICollection<Message> Messages { get; set; }
}

public class UserConfig : EntityTypeConfiguration<User>
{
 public UserConfig()
 {
  HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser).HasForeignKey(x => x.SenderUserId);
  HasMany(x => x.CurrentUserConversations).WithOptional(x => x.CurrentUser).HasForeignKey(x => x.CurrentUserId);
 }
}

如何使用它:

var user = myDbContext.Users.First();

var currentUser = new User { Active = true, Id = Guid.NewGuid() };
myDbContext.Users.Add(currentUser);

var message = new Message { IsDelivered = true };

var conversation = new Conversation() { ID = Guid.NewGuid() };
conversation.Messages.Add(message);

conversation.CurrentUser = user;
currentUser.Conversations.Add(conversation);

myDbContext.Conversations.Add(conversation);
myDbContext.SaveChanges();

根据您的代码,我已添加了以下行:public ICollection<Conversation> CurrentUserConversations { get; set; }``myDbContext.Conversations.Add(conversation); 为什么需要添加 myDbContext.Conversations.Add(conversation);?由于这是一个聊天应用程序,如何查询另一个用户和我之间的对话,例如:如果另一个用户先给我发消息了? - Ajay Kumar
@AjayVerma,那只是一个代码示例。你已经存储了当前用户和其他用户的对话,每次当前用户获取新的对话时,都会将新的对话ID添加到数据库中,因此你可以搜索对话。选择(c => currentUserId == currentUser.Id)并找到相应的对应者。 - Bassam Alugili
@AjayVerma 抱歉,我想帮助你,但我没有你的代码。在这个链接中阅读Inverse属性,你会找到一个好的教程,这将帮助你理解问题http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx,并阅读这个链接2 http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx 解决方案是使用Code-First完成的,而你的解决方案是Fleunt API,但概念是相同的。 - Bassam Alugili
有很多方法可以实现,但是您的对话可以存储两个用户,一个用于发送,一个用于接收,或者只有一个用户作为接收者和一个ID作为发送者。顺便说一下,不建议使用Guid作为ID。 - Bassam Alugili
嗨。它仍处于开发模式,所以我一定会将Guid更改为long或int。如果有其他实现普通消息并稍后扩展到聊天的方法,我非常乐意倾听。你能告诉我其中之一吗? - Ajay Kumar
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接