EF Core SQLITE - SQLite 错误 19: '唯一约束条件失败

8
我是一名正在学习使用Entity Framework Core操作SQLite数据库的人,我的数据库中有2个表。
表1: Messages:
CREATE TABLE `Messages` (
    `Id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `IsDeleted` INTEGER NOT NULL,
    `FromUserId`    INTEGER,
    `ToUserId`  INTEGER,
    `SendDate`  TEXT NOT NULL,
    `ReadDate`  TEXT NOT NULL,
    `MessageContent`    TEXT,
    CONSTRAINT `FK_Messages_Users_ToUserId` FOREIGN KEY(`ToUserId`) REFERENCES `Users`(`Id`) ON DELETE RESTRICT,
    CONSTRAINT `FK_Messages_Users_FromUserId` FOREIGN KEY(`FromUserId`) REFERENCES `Users`(`Id`) ON DELETE RESTRICT
);

和用户表:

CREATE TABLE `Users` (
    `Id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    `IsDeleted` INTEGER NOT NULL,
    `Name`  TEXT,
    `DisplayName`   TEXT,
    `Gender`    INTEGER NOT NULL,
    `BirthDate` TEXT NOT NULL
);

我的C#类看起来像:

public class User
{
    public int Id {get;set;}
    public bool IsDeleted{get;set;}
    [Required]
    public string Name{get;set;}

    public string DisplayName{get;set;}

    [Required]
    public char Gender{get;set;}

    [Required]
    public DateTime BirthDate{get;set;}

    public User(string n, string dn, char g, DateTime bd)
    {
        Name=n; DisplayName = dn; Gender = g; BirthDate = bd;
    }

    protected User(){}

    public override string ToString()
    {
        return string.Format("{0} ({1}) {2}", this.Name, this.Gender,this.BirthDate.ToShortDateString());
    }
}

和 Messages 类:

public class Message 
{
    public int Id{get;set;}
    public bool IsDeleted{get;set;}
    [Required]
    public Users.User FromUser{get;set;}
    
    [Required]
    public Users.User ToUser{get;set;}

    [Required]
    public DateTime SendDate{get;set;}
    public DateTime? ReadDate{get;set;}

    [Required]
    public string MessageContent{get;set;}

    protected Message(){}

    public Message(User from, User to, string content)
    {
        this.FromUser = from;
        this.ToUser = to;
        this.MessageContent = content;
        this.SendDate = DateTime.Now;
        this.ReadDate = DateTime.Now;
    }

}

用户表正常工作,但当我尝试通过以下方式向消息添加新实体时:

public override int Insert(Message entity)
{
    dbc.Messages.Add(entity);
    dbc.SaveChanges();
    return entity.Id;
}

我遇到了以下错误:

SQLite错误19:'唯一约束失败'

我不知道出了什么问题。当我手动向数据库插入数据时(使用DB browser for sqlite),它可以工作。类之间的关系是否正确?

我的dbContextSnapshot是:

[DbContext(typeof(RandevouDbContext))]
partial class RandevouDbContextModelSnapshot : ModelSnapshot
{
    protected override void BuildModel(ModelBuilder modelBuilder)
    {
#pragma warning disable 612, 618
    modelBuilder.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");

    modelBuilder.Entity("RandevouData.Messages.Message", b =>
        {
            b.Property<int>("Id")
                .ValueGeneratedOnAdd();

            b.Property<int>("FromUserId");

            b.Property<bool>("IsDeleted");

            b.Property<string>("MessageContent")
                .IsRequired();

            b.Property<DateTime?>("ReadDate");

            b.Property<DateTime>("SendDate");

            b.Property<int>("ToUserId");

            b.HasKey("Id");

            b.HasIndex("FromUserId");

            b.HasIndex("ToUserId");

            b.ToTable("Messages");
        });

    modelBuilder.Entity("RandevouData.Users.User", b =>
        {
            b.Property<int>("Id")
                .ValueGeneratedOnAdd();

            b.Property<DateTime>("BirthDate");

            b.Property<string>("DisplayName");

            b.Property<char>("Gender");

            b.Property<bool>("IsDeleted");

            b.Property<string>("Name")
                .IsRequired();

            b.HasKey("Id");

            b.ToTable("Users");
        });

    modelBuilder.Entity("RandevouData.Messages.Message", b =>
        {
            b.HasOne("RandevouData.Users.User", "FromUser")
                .WithMany()
                .HasForeignKey("FromUserId")
                .OnDelete(DeleteBehavior.Cascade);

            b.HasOne("RandevouData.Users.User", "ToUser")
                .WithMany()
                .HasForeignKey("ToUserId")
                .OnDelete(DeleteBehavior.Cascade);
        });
#pragma warning restore 612, 618
}

顺便说一下,我无法配置双向映射。消息实体具有UserFrom和UserTo字段,但用户实体不能拥有消息,因为他有时是“userFrom”,有时是“userTo”。还有一段代码,我在其中创建了消息实体。
        public int SendMessage(int senderId, int receiverId, string content)
    {
        var dao = new MessagesDao();
        var usersDao = new UsersDao();
        var userService = new UserService(mapper);
        
        var sender = usersDao.Get(senderId);
        var receiver = usersDao.Get(receiverId);
        
        var entity = new Message(sender,receiver,content);
        var id = dao.Insert(entity);
        return id;
    }

用户 DAO,在其中覆盖了 GET 方法

 public override User Get(int id)
        {
            using (var context = new RandevouDbContext())
            { 
                var user = dbc.Find<User>(id);
                return user;
            }
        }

和MessagesDao,我重写Add方法

 public override int Insert(Message entity)
        {
            using (var dc = new RandevouDbContext())
            {
                dc.Messages.Add(entity);
                dc.SaveChanges();
                return entity.Id;
            }
        }

顺便说一句,我不知道这是否正常,但我有0个条目需要更新(!?) 在此输入图片描述

3个回答

13

要解决“UNIQUE”约束异常,您需要确保所有要处理的实体都被上下文跟踪,以保存到所需的上下文中。

例如:

// Get user from one context

User thisUser = db.Users.Find(3);

//Now imagine you have another context which has been created later on

DbContext newContext = new DbContext();

//and you want to create a message, assign it to your user 
//and save it to the new context

Message newMessage = new Message
{
    Text = "Hello",
    User = thisUser
}

newContext.Messages.Add(newMessage);

// saving this to newContext will fail because newContext is not tracking thisUser,
// Therefore attach it...

newContext.Users.Attach(thisUser).

//now newContext is tracking thisUser *AND* newMessage, so the save operation will work

newContext.SaveChanges();

7

好的,我解决了问题。 阅读了关于 EF DbContext 的一些文章。 当我为获取用户和创建消息使用两个其他上下文时, 实体(user1 和 user2)与我用于第二个上下文不同。我的意思是,实体是相同的,但它们有另一个跟踪。

EF Core / Sqlite 一对多关系在唯一索引约束上失败

所以我通过在一个业务交易中使用一个上下文(而不是一个数据库事务)来解决问题。只需将创建上下文移动到业务逻辑中,通用 dao 类在构造函数中等待上下文,就像这样:

public class MessagesDao : BasicDao<Message>
    {
        public MessagesDao(RandevouDbContext dbc) : base(dbc)
        {

        }
        public IQueryable<Message> QueryMessages()
        {
                return dbc.Messages.AsQueryable();

        }

        public override int Insert(Message entity)
        {
            dbc.Messages.Add(entity);
            dbc.SaveChanges();
            return entity.Id;

        }
    }



 public abstract class BasicDao<TEntity> where TEntity : BasicRandevouObject
    {
        protected RandevouDbContext dbc;
        public BasicDao(RandevouDbContext dbc) { this.dbc = dbc; }

        public virtual int Insert(TEntity entity)
        {
                dbc.Add<TEntity>(entity);
                dbc.SaveChanges();
                return entity.Id;

        }

        public virtual void Update(TEntity entity)
        {
                dbc.Update(entity);
                dbc.SaveChanges();

        }

        public virtual void Delete(TEntity entity)
        {
                dbc.Remove(entity);
                dbc.SaveChanges();

        }

        public virtual TEntity Get(int id)
        {
                var entity = dbc.Find<TEntity>(id);
                return entity;

        }


    }

在业务逻辑代码中:

public int SendMessage(int senderId, int receiverId, string content)
        {
            using (var dbc = new RandevouDbContext())
            { 
                var dao = new MessagesDao(dbc);
            var usersDao = new UsersDao(dbc);
            var userService = new UserService(mapper);

            var sender = usersDao.Get(senderId);
            var receiver = usersDao.Get(receiverId);

            var entity = new Message(sender,receiver,content);
            var id = dao.Insert(entity);
            return id;
            }
        }

现在一切都正常了。顺便说一下,这仍然是肮脏的代码,因为我只是尝试一些东西,但我希望如果有人遇到类似的问题,他会找到这篇文章。


谢谢你!我在使用Sqlite和blazor .NET 6应用程序时遇到了同样的问题。你上面的帖子帮助我理解了。我(错误地)将我的服务添加为瞬态,将其更改为Scoped解决了我的问题。谢谢! - Opi

5
你(非常详细)的问题缺少的是你传递给Insert()Message构成。你之前是否将这两个User保存到数据库中,或者它们也是两个新对象吗? 如果它们是新的,则它们的ID应该为零,这样EF才知道先创建它们,然后再创建Message

那么,在这种情况下,您是否已经获取并将两个用户组合到消息字段中了?您应该将 Message._xx_User 字段设置为从保存请求返回的组合用户,或者执行 .Get() 以检索组合的 User 记录并在 Message 记录中设置它们。 - Scott Leckie
我不明白。在创建消息之前,我已经对两个用户都执行了dao.Get<user>(userId)。接下来,我创建了一个需要(user u1, users u2, string content)参数的消息。在用户类中,我没有消息属性。 - SharkyShark
听起来你正在做我建议的事情。你能否扩展你最初的问题并展示保存用户、获取用户和保存消息的代码? - Scott Leckie
你在Insert()方法中使用的那个dbcontext(dbc)是在其他地方构建的吗?它是用于保存用户和消息的相同上下文吗? 如果它不是相同的上下文,那么它是在用户更改被保存之前初始化的吗?如果是这样,那么它不知道新的用户记录。 通常,上下文应该非常短暂;我会在Insert()方法内部构建一个新的上下文... - Scott Leckie
即使我每次创建新的数据库上下文,仍然存在相同的问题: SqliteException:SQLite错误19:“唯一约束失败:Users.Id”。 我还在课程中阅读到不要一直创建新的DbContext。 - SharkyShark
很高兴你解决了这个问题 - 看起来问题就像我所建议的那样,消息上下文没有意识到用户上下文的变化 - 共享上下文将解决你的问题。 - Scott Leckie

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