EntityFramework进阶——数据变更冲突

TimeStamp

更新操作可能伴随数据冲突,我们可以通过并发处理妥善解决这一方面的问题。避免数据冲突比较方便的做法是自动加入字节数组(byte[])类型的TimeStamp属性,对应到数据表中的rowvewsion类型字段,自动监控数据的更新操作。

下面通过一个例子来说明:

新建一个项目,名称为TimeStampDemo,新增实体类如下图所示:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
        public int Quantity { get; set; }
        [Timestamp]
        public byte[] Timestamp { get; set; }
    }

上下文代码如下图所示:

    public class TimeStampModel : DbContext
    {
        public TimeStampModel()
            : base("name=TimeStampModel")
        {
        }

        public virtual DbSet<Product> Product { get; set; }
    }

首次运行项目,在数据库中生成了如下表结构:

                                              

下面编写程序来查看其中的Timestamp字段值:

static void Main(string[] args)
{
    using (TimeStampModel model = new TimeStampModel())
    {
         Product product = model.Product.Where(p => p.Id == 1).First();
         long ts = BitConverter.ToInt64(product.Timestamp,0);
         Console.WriteLine("Quantity:{0} \t Timestamp:{1}",product.Quantity,ts);


         product.Quantity = 150;
         model.SaveChanges();
         ts = BitConverter.ToInt64(product.Timestamp,0);
         Console.WriteLine("Quantity:{0} \t Timestamp:{1}",product.Quantity,ts);
         Console.ReadKey();
    }
}

运行效果下图所示:

                 

可以看到,每一次更新数据时,EF都会修改Timestamp的值。如果期间有其他程序抢先完成更新造成数据变更,那么EF就会报错。

我们重新执行程序,在首次出现Timestamp语句时,打上断点。然后在SQL查询界面直接更新用SQL语句更新数据:

UPDATE dbo.Products SET Quantity = 200 WHERE Id = 1

SQL执行之前Timestamp的值: 

                                 

SQL执行之后Timestamp的值:

                                

可以看到每次更新数据后,Timestamp的数值会发生改变, 这时继续运行程序,就会报错:

 

此信息描述找不到要更新的数据,这是因为EF在更新每一项数据时,除了主键之外,还会对比当时取出的TimeStamp的值。由于我们使用外部SQL更新,EF无法检测这个字段就出现报错。

异常DbUpdateConcurrencyException派生自DbUpdateException,表示一个并发更新冲突,当这个异常出现时,显示SaveChanges更新失败,通过DbUpdateException.Entries属性可以获取其返回的IEnumerable<DbEntityEntry>对象,其中存取未成功更新的实体数据对象。 

重新调整Main方法中的代码如下图所示:

static void Main(string[] args)
{
     using (TimeStampModel model = new TimeStampModel())
     {
          Product product = model.Product.Where(p => p.Id == 1).First();
          long ts = BitConverter.ToInt64(product.Timestamp, 0);
          Console.WriteLine("Quantity:{0} \t TimeStamp:{1}", product.Quantity, ts);

           try
           {
               Console.WriteLine("新的 Quantity 值:");
               int quantity = 500;
               product.Quantity = quantity;
               model.SaveChanges();

               ts = BitConverter.ToInt64(product.Timestamp, 0);
               Console.WriteLine("Quantity:{0} \t TimeStamp:{1}", product.Quantity, ts);
           }
           catch (DbUpdateConcurrencyException ex)
           {
               DbEntityEntry entry = ex.Entries.Single();
               DbPropertyValues current = entry.CurrentValues;//当前实体数据
               int quantity = current.GetValue<int>("Quantity");
               long timestamp = BitConverter.ToInt64(current.GetValue<byte[]>("Timestamp"),0);

               DbPropertyValues dbvalue = entry.GetDatabaseValues();//数据库中的实体数据
               int dbquantity = dbvalue.GetValue<int>("Quantity");
               long dbtimestamp = BitConverter.ToInt64(dbvalue.GetValue<byte[]>("Timestamp"),0);

               Console.WriteLine("DbUpdateConcurrentException.....");
               Console.WriteLine("当前实体的属性值: Quantity:{0} \t TimeStamp:{1}",quantity,timestamp);

               Console.WriteLine("数据库中的字段值:Quantity:{0} \t TimeStamp:{1}",dbquantity,dbtimestamp);
                }
            }
}

再次在首次出现Timestamp语句时,打上断点。然后在SQL查询界面直接更新用SQL语句更新数据后,继续运行程序,如下图所示:

               

 


并发冲突处理——Database Wins 或者 Client Wins 

处理更新冲突有几种方式,想以数据库中的值来覆盖当前数据对象的值,则可以调用DbEntityEntry类定义的Reload方法,此方法执行的是Database Wins(数据库优先)策略,执行完毕之后,数据对象当前的值将与数据库同步。

entry.Relaod();
entry.SaveChanges();

另一种方式则是反向以当前的值覆盖数据库的值,称为Clinet  Wins(客户优先)策略。

entry.OriginalValues.SetValues(entry.GetDatabaseValues());
model.SaveChanges()

 

添加一个ResolveConcurrency方法来支持并发冲突处理:

public static void ResolveConcurrency(TimeStampModel model, DbEntityEntry entry)
{
    Console.WriteLine("1.Database wins  2.Client wins:");
    int i = int.Parse(Console.ReadLine());
    if (i == 1)
    {
        entry.Reload();
        model.SaveChanges();
        Console.WriteLine("与数据库同步完成——DataBase Wins");
    }
    else
    {
        entry.OriginalValues.SetValues(entry.GetDatabaseValues());
        model.SaveChanges();
        Console.WriteLine("重新更新数据库完成——Client Wins");
    }
}

ResolveConcurrency方法插入上述例子的catch语句中,并在并发冲突中选择与数据库同步完成,结果如下图所示:

                          

 


ConcurrencyCheck注解 

TimeStamp注解属性会导致EF在更新时监控整项数据的更新状态,如果对特定字段进行数据冲突的监控,则可以通过ConcurrencyCheck注解来达到目的。

下面通过程序来说明,新建一个控制台应用程序ConcurrencyCheckDemo,有如下一个实体类:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Price { get; set; }
        [ConcurrencyCheck]
        public int SPrice { get; set; }
    }

在特价字段(SPrice)设置【ConcurrencyCheck】,当程序更新任何一项Product数据时,SPrice字段将受到监控,期间不受EF监控的程序变更了SPrice字段的值时,便会产生冲突。其他字段没有设置,因此数据的更新不会有冲突。

在Main函数中增加如下图代码:

static void Main(string[] args)
{
    using (ConcurrencyCheckModel db = new ConcurrencyCheckModel())
    {
         try
         {
               Console.WriteLine("指定更新操作:A.商品名称 B.商品特价");
               string ab = Console.ReadLine();
               if (ab == "A")
               {
                   Console.WriteLine("输入第一项商品的新名称:");
                   string name = Console.ReadLine();
                   db.Product.First().Name = name;
               }
               if (ab == "B")
               {
                    Console.WriteLine("输入第一项商品的新特价:");
                    int price = int.Parse(Console.ReadLine());
                    db.Product.First().SPrice = price;
               }
               Console.WriteLine("按任意键完成更热");
               Console.ReadKey();
               db.SaveChanges();
               Console.WriteLine("完成更新");
               }
               catch (Exception ex)
               {
                    Console.WriteLine(ex.ToString());
                    Console.WriteLine(ex.Message);
               }
         }
}

当修改商品特价时,去数据库修改SPrice字段的值,便会报错:

       

 


 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Entity Framework 可以实现多数据上传,可以通过以下步骤实现: 1. 创建一个包含所有数据上传的实体类。 2. 实现一个上传数据的方法,使用 Entity Framework 进行数据保存。 3. 在上传数据的方法中,使用事务来确保数据的完整性。 4. 在上传数据的方法中,使用 DbContext 类的实例来操作数据库。 5. 在上传数据的方法中,使用 DbSet 类的实例来操作特定的实体。 6. 在上传数据的方法中,使用 LINQ 查询语言来操作数据。 例如,以下代码展示了如何在 Entity Framework 中实现多数据上传: ``` public void UploadData(List<MyEntity> entities) { using (var context = new MyDbContext()) { using (var transaction = context.Database.BeginTransaction()) { try { foreach (var entity in entities) { context.MyEntities.Add(entity); } context.SaveChanges(); transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); throw ex; } } } } ``` 在这个示例中,我们创建了一个名为 MyEntity 的实体类,并实现了一个 UploadData 方法,该方法接受一个 MyEntity 类型的列表作为参数。在方法中,我们使用 Entity Framework 的 DbContext 类的实例来打开数据库连接,并使用 MyDbContext 类来操作数据库。我们还使用事务来确保数据的完整性,如果在上传数据的过程中发生任何错误,我们将回滚事务。最后,我们使用 SaveChanges() 方法来保存所有更改,并使用 Commit() 方法提交事务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值