一. 说明

EF版本的事务介绍详见:

  第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)。

  本节主要介绍EF Core下的三种事务的用法和各自的使用场景,其中SaveChanges和DBContextTransaction事务与EF版本的基本一致,在该章节中补充一些新的使用场景和配置方式,TransactionScope环境事务与EF 版本的有着本质的区别,它目前不支持分布式数据库事务。

  后面章节将继续介绍事务的基础概念、事务的隔离级别和带来的各种问题。

二. 默认事务(SaveChanges)

(1).默认情况下,如果数据库提供程序支持事务,单个 SaveChanges() 调用中的所有变更都会在一个事务中被提交。如果其中任何一个变更失败了, 那么事务就会回滚,没有任何变更会被应用到数据库。这意味着 SaveChanges() 能够确保要么成功保存,要么在发生错误时不对数据库做任何修改。

(2).关闭默认事务:context.Database.AutoTransactionsEnabled = false; 如:Test3()方法,第一条数据保存成功,第二条失败。

 代码分享:

         /// <summary>
/// 全部成功
/// </summary>
public static void Test1()
{
using (EFDB01Context db = new EFDB01Context())
{
db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员2", addTime = DateTime.Now });
db.SaveChanges();
}
} /// <summary>
/// 全部失败
/// </summary>
public static void Test2()
{
using (EFDB01Context db = new EFDB01Context())
{
try
{
db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
db.T_RoleInfor.Add(new T_RoleInfor() { id = , roleName = "管理员2", addTime = DateTime.Now });
db.SaveChanges();
}
catch (Exception)
{
Console.WriteLine("出错了,两条数据都没有执行成功");
}
}
} /// <summary>
/// 第一条成功,第二条失败
/// </summary>
public static void Test3()
{
using (EFDB01Context db = new EFDB01Context())
{
try
{
//关闭SaveChanges的默认事务
db.Database.AutoTransactionsEnabled = false; db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
db.T_RoleInfor.Add(new T_RoleInfor() { id = , roleName = "管理员2", addTime = DateTime.Now }); //db.T_UserInfor.Add(new T_UserInfor() { id = Guid.NewGuid().ToString("N"), userName = "管理员1", addTime = DateTime.Now });
//db.T_UserInfor.Add(new T_UserInfor() { id = Guid.NewGuid().ToString("N")+"123", userName = "管理员2", addTime = DateTime.Now }); db.SaveChanges();
}
catch (Exception)
{
Console.WriteLine("出错了,第一条数据插入成功了");
}
}
}

三. DbContextTransaction

1. 使用方式

  BeginTransaction开启事务、Commit提交事务、Rollback回滚事务、Dispose销毁,如果用Using包裹的话,不再需要手动Rollback,走完Using会自动回滚。如果不用Using包裹事务,就需要在Catch中手动RollBack回滚,并且最好最后手动的Dispose一下。(如SameDbContext文件夹中的Test1和Test2方法)

2. 使用场景

 A. 同一个上下文多个SaveChanges的方法(如:自增主键后续要用到,如Test2方法)、SaveChanges和EF调用SQL语句混用(如Test2方法)

        /// <summary>
/// 三条添加语句共享同一个事务,最后使用 transaction.Commit() 统一提交,三条全部执行成功,则影响到数据库,
/// 如果任何一个命令失败,则在事务被回收(Dispose)时会自动回滚,对数据库无影响。
/// </summary>
public static void Test1()
{
using (EFDB01Context db = new EFDB01Context())
{
using (var transaction = db.Database.BeginTransaction())
{
try
{
db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
db.SaveChanges(); db.T_RoleInfor.Add(new T_RoleInfor() { id = , roleName = "管理员2", addTime = DateTime.Now }); //报错
db.SaveChanges(); string sql1 = @"insert into T_RoleInfor (roleName,roleDescription,addTime) values (@roleName,@roleDescription,@addTime)";
SqlParameter[] pars1 ={
new SqlParameter("@roleName","管理员3"),
new SqlParameter("@roleDescription","txt11"),
new SqlParameter("@addTime",DateTime.Now)
};
db.Database.ExecuteSqlCommand(sql1, pars1);
transaction.Commit(); Console.WriteLine("成功了");
}
catch (Exception)
{
Console.WriteLine("失败了");
}
}
}
} /// <summary>
/// 如果不用Using包裹事务,就需要在Catch中手动RollBack回滚
/// </summary>
public static void Test2()
{
using (EFDB01Context db = new EFDB01Context())
{
var transaction = db.Database.BeginTransaction();
try
{
var d1 = new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now };
db.T_RoleInfor.Add(d1);
db.SaveChanges(); db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员2"+d1.id, addTime = DateTime.Now });
db.SaveChanges(); string sql1 = @"insert into T_RoleInfor (roleName,roleDescription,addTime) values (@roleName,@roleDescription,@addTime)";
SqlParameter[] pars1 ={
new SqlParameter("@roleName","管理员3"),
new SqlParameter("@roleDescription","txt11"),
new SqlParameter("@addTime",DateTime.Now)
};
db.Database.ExecuteSqlCommand(sql1, pars1);
transaction.Commit(); Console.WriteLine("成功了"); }
catch (Exception)
{
transaction.Rollback();
Console.WriteLine("失败了");
}
finally
{
transaction.Dispose();
} }
}

 B. 同一个数据库多个上下文但“同一个连接”的事务。其中一个上下文开启事务,另外上下文通过UseTransaction方法来实现共享事务。

 情况①:

  EFDB01Context直接在OnConfiguring中写死连接字符串,多次new上下文,如Test1方法,则是多个连接,不能共享事务。

  /// <summary>
/// 情况一:在OnConfiguring中书写连接字符串,创建两个上下文,相当于两个连接,两个连接之间不能通过使用UseTransaction,建立事务连接。
/// 会报下面的错。
/// The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.
/// </summary>
public static void Test1()
{
using (EFDB01Context context1 = new EFDB01Context())
{
using (var transaction = context1.Database.BeginTransaction())
{
try
{
context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges(); using (EFDB01Context context2 = new EFDB01Context())
{
context2.Database.UseTransaction(transaction.GetDbTransaction()); context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges();
} //统一提交
transaction.Commit();
Console.WriteLine("成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
} }

 情况②:

  EFDB01Context2通过 public EFDB01Context2(DbContextOptions<EFDB01Context2> options) : base(options)这种形式的构造函数,然后new的时候 统一传入: new  DbContextOptionsBuilder<EFDB01Context2>().UseSqlServer(new SqlConnection(connectionString)).Options;,从而共享连接,如Test2方法。

         /// <summary>
/// 情况二:通过父类构造函数
/// </summary>
public static void Test2()
{
var connectionString = "Server=localhost;Database=EFDB01;User ID=sa;Password=123456;";
//将连接拿出来,传到多个上下文中,这样是共享同一个连接
var option = new DbContextOptionsBuilder<EFDB01Context2>().UseSqlServer(new SqlConnection(connectionString)).Options; using (var context1 = new EFDB01Context2(option))
{
using (var transaction = context1.Database.BeginTransaction())
{
try
{
context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges(); using (var context2 = new EFDB01Context2(option))
{
context2.Database.UseTransaction(transaction.GetDbTransaction()); context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges();
} //统一提交
transaction.Commit();
Console.WriteLine("成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

 情况③:

 EFDB01Context3通过 传入SqlConnection来实现共享连接,如Test3方法。

         public static void Test3()
{
var connectionString = "Server=localhost;Database=EFDB01;User ID=sa;Password=123456;";
//将连接拿出来,传到多个上下文中,这样是共享同一个连接
var connection = new SqlConnection(connectionString); using (var context1 = new EFDB01Context3(connection))
{
using (var transaction = context1.Database.BeginTransaction())
{
try
{
context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges(); using (var context2 = new EFDB01Context3(connection))
{
context2.Database.UseTransaction(transaction.GetDbTransaction()); context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context1.SaveChanges();
} //统一提交
transaction.Commit();
Console.WriteLine("成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

 C. 多种数据库技术同一个数据库的事务

 如ADO.Net和EF共同使用,利用方法 “UseTransaction”共享同一个事务,共同提交。 如:Test1方法

         /// <summary>
/// ADO.Net 和 EF混用,多种数据库技术访问同一个数据库
/// </summary>
public static void Test1()
{
var conStr = @"Server=localhost;Database=EFDB01;User ID=sa;Password=123456;";
using (var connection=new SqlConnection(conStr))
{
connection.Open();
using (var transaction=connection.BeginTransaction())
{
try
{
//ADO.Net
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM T_RoleInfor";
command.ExecuteNonQuery(); //EF
var options = new DbContextOptionsBuilder<EFDB01Context>().UseSqlServer(connection).Options;
using (var context = new EFDB01Context(options))
{
context.Database.UseTransaction(transaction);
context.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context.SaveChanges();
}
//综合提交
transaction.Commit(); Console.WriteLine("成功了"); }
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
} }
}

四. 环境事务

1.使用方式

  new TransactionScope创建事务、Complete提交事务、 Transaction.Current.Rollback();回滚事务、Dispose销毁对象。如果用Using包裹的话,不再需要手动Rollback,走完Using会自动回滚。如果不用Using包裹事务,就需要在Catch中手动RollBack回滚,并且最好最后手动的Dispose一下。

2.用途

 A. 同一个上下文的事务。(多个SaveChanges(自增主键后续用到的情况)、SaveChanges和EF调用SQL语句混用)(如Test1方法)

  /// <summary>
/// A. 同一个上下文的事务。(多个SaveChanges(自增主键后续用到的情况)、SaveChanges和EF调用SQL语句混用)
/// </summary>
public static void Test1()
{
using (EFDB01Context1 db = new EFDB01Context1())
{
using (var scope = new TransactionScope(/*TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }*/))
{
try
{
var data1 = new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now };
db.T_RoleInfor.Add(data1);
db.SaveChanges(); db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员2" + data1.id, addTime = DateTime.Now }); //报错
db.SaveChanges(); string sql1 = @"insert into T_RoleInfor (roleName,roleDescription,addTime) values (@roleName,@roleDescription,@addTime)";
SqlParameter[] pars1 ={
new SqlParameter("@roleName","管理员3"),
new SqlParameter("@roleDescription","txt11"),
new SqlParameter("@addTime",DateTime.Now)
};
db.Database.ExecuteSqlCommand(sql1, pars1);
scope.Complete(); Console.WriteLine("成功了");
}
catch (Exception)
{
Console.WriteLine("失败了");
}
}
}
}

 B. 多种数据库技术访问同一个数据库的事务 (如Test2方法)

         /// <summary>
/// B. 多种数据库技术访问同一个数据库的事务
/// </summary>
public static void Test2()
{
var conStr = @"Server=localhost;Database=EFDB01;User ID=sa;Password=123456;";
using (var connection = new SqlConnection(conStr))
{
connection.Open();
using (var scope = new TransactionScope())
{
try
{
//ADO.Net
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM T_RoleInfor";
command.ExecuteNonQuery(); //EF
using (var context = new EFDB01Context1())
{
context.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context.SaveChanges();
}
//综合提交
scope.Complete(); Console.WriteLine("成功了"); }
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

 C. 同一个数据库多个不同的上下文是支持的(如Test3方法)

上下文代码:

   public partial class EFDB01Context1 : DbContext
{
public virtual DbSet<T_RoleInfor> T_RoleInfor { get; set; }
public virtual DbSet<T_UserInfor> T_UserInfor { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=EFDB01;User ID=sa;Password=123456;");
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); modelBuilder.Entity<T_RoleInfor>(entity =>
{
entity.Property(e => e.roleDescription).IsUnicode(false); entity.Property(e => e.roleName).IsUnicode(false);
}); modelBuilder.Entity<T_UserInfor>(entity =>
{
entity.Property(e => e.id)
.IsUnicode(false)
.ValueGeneratedNever(); entity.Property(e => e.userName).IsUnicode(false); entity.Property(e => e.userSex).IsUnicode(false);
});
}
}

EFDB01Context1

   public partial class EFDB01Context2 : DbContext
{
public virtual DbSet<T_RoleInfor> T_RoleInfor { get; set; }
public virtual DbSet<T_UserInfor> T_UserInfor { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=EFDB01;User ID=sa;Password=123456;");
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); modelBuilder.Entity<T_RoleInfor>(entity =>
{
entity.Property(e => e.roleDescription).IsUnicode(false); entity.Property(e => e.roleName).IsUnicode(false);
}); modelBuilder.Entity<T_UserInfor>(entity =>
{
entity.Property(e => e.id)
.IsUnicode(false)
.ValueGeneratedNever(); entity.Property(e => e.userName).IsUnicode(false); entity.Property(e => e.userSex).IsUnicode(false);
});
}
}

EFDB01Context2

运行代码:

        /// <summary>
///C. 同一个数据库两个不同上下文是支持的
/// </summary>
public static void Test3()
{
using (var scope = new TransactionScope())
{
try
{
using (var context = new EFDB01Context1())
{
context.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context.SaveChanges();
}
using (var context = new EFDB01Context2())
{
context.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context.SaveChanges();
} //综合提交
scope.Complete(); Console.WriteLine("成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

 D. 不同数据库的上下文是不支持的,(如Test4方法,开启msdtc服务的步骤: cmd命令→ net start msdtc ,然后发现报错:This platform does not support distributed transactions.说明目前Core平台下不支持分布式事务)

上下文代码:

  public partial class dbCore1Context : DbContext
{
public dbCore1Context()
{
} public dbCore1Context(DbContextOptions<dbCore1Context> options)
: base(options)
{
} public virtual DbSet<UserInfors> UserInfors { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=dbCore1;User ID=sa;Password=123456;"); } protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); modelBuilder.Entity<UserInfors>(entity =>
{
entity.Property(e => e.id).ValueGeneratedNever();
});
}
}

dbCore1Context

   public partial class EFDB01Context1 : DbContext
{
public virtual DbSet<T_RoleInfor> T_RoleInfor { get; set; }
public virtual DbSet<T_UserInfor> T_UserInfor { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;Database=EFDB01;User ID=sa;Password=123456;");
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); modelBuilder.Entity<T_RoleInfor>(entity =>
{
entity.Property(e => e.roleDescription).IsUnicode(false); entity.Property(e => e.roleName).IsUnicode(false);
}); modelBuilder.Entity<T_UserInfor>(entity =>
{
entity.Property(e => e.id)
.IsUnicode(false)
.ValueGeneratedNever(); entity.Property(e => e.userName).IsUnicode(false); entity.Property(e => e.userSex).IsUnicode(false);
});
}
}

EFDB01Context1

运行代码:

         /// <summary>
///D. 不同数据库之间的事务
/// </summary>
public static void Test4()
{
using (var scope = new TransactionScope())
{
try
{
using (var context = new EFDB01Context1())
{
context.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now });
context.SaveChanges();
}
using (var context = new dbCore1Context())
{
context.UserInfors.Add(new UserInfors() { id = Guid.NewGuid().ToString("N"), userName = "管理员1", userSex = "男" });
context.SaveChanges();
} //综合提交
scope.Complete(); Console.WriteLine("成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

 注:EF Core中的 System.Transactions 实现将不包括对分布式事务的支持,因此不能使用 TransactionScope 或 CommittableTransaction 来跨多个资源管理器协调事务。主要分布式事务需要依赖于 Windows 系统的 MSDTC 服务,但.NET Core要实现跨平台,基于跨平台的分布式事务没有统一的标准,后续版希望改进。

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

第五节:EF Core中的三类事务(SaveChanges、DbContextTransaction、TransactionScope)的更多相关文章

  1. 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)

    原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ...

  2. EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解

    EF Core中: 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体. 此外调用Queryable.Join方法返回的匿名类型也不会被DbCont ...

  3. [小技巧]EF Core中如何获取上下文中操作过的实体

    原文地址:https://www.cnblogs.com/lwqlun/p/10576443.html 作者:Lamond Lu 源代码:https://github.com/lamondlu/EFC ...

  4. EF Core中避免贫血模型的三种行之有效的方法(翻译)

    Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...

  5. EF Core中的多对多映射如何实现?

    EF 6.X中的多对多映射是直接使用HasMany-HasMany来做的.但是到了EF Core中,不再直接支持这种方式了,可以是可以使用,但是不推荐,具体使用可以参考<你必须掌握的Entity ...

  6. EF Core中执行Sql语句查询操作之FromSql,ExecuteSqlCommand,SqlQuery

    一.目前EF Core的版本为V2.1 相比较EF Core v1.0 目前已经增加了不少功能. EF Core除了常用的增删改模型操作,Sql语句在不少项目中是不能避免的. 在EF Core中上下文 ...

  7. EF Core中DbContext可以被Dispose多次

    我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...

  8. 9.4 翻译系列:EF 6以及 EF Core中的NotMapped特性(EF 6 Code-First系列)

    原文链接:http://www.entityframeworktutorial.net/code-first/notmapped-dataannotations-attribute-in-code-f ...

  9. EF Core中Join可以进行子查询

    我们来看看下面的代码,这个代码是一个INNER JOIN的EF Core查询,其中用SubCategory表INNER JOIN了SubCategoryLanguage表,但是我们需要在SubCate ...

随机推荐

  1. JavaScript Location 对象用法

    Location 对象 Location对象包含有关当前URL的信息.location对象是window对象的一部分,可以通过window.location属性访问. 注意:没有适用于location ...

  2. java8新特性—四大内置核心接口

    java8新特性-四大内置核心接口 四大内置核心接口 //消费型接口 Consumer<T>:: vode accept(T t); //供给型接口 Supplier<T>:: ...

  3. bat脚本输出日志

    一,bat脚本常用的的输出命令是“echo 输出内容”,可以利用echo将文本输出到cmd窗口或文件.bat脚本是不区分大小写的脚本语言,因此大小写都可以. 例如: REM 在命令行窗口输出 echo ...

  4. EM算法-完整推导

    前篇已经对EM过程,举了扔硬币和高斯分布等案例来直观认识了, 目标是参数估计, 分为 E-step 和 M-step, 不断循环, 直到收敛则求出了近似的估计参数, 不多说了, 本篇不说栗子, 直接来 ...

  5. Nginx02(环境配置以及基本使用)

    一:Nginx环境配置 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet ...

  6. apache主配置文件设置

    主配置文件:httpd.conf #设置管理员邮箱地址ServerAdmin admin@example.com#定义apache安装根目录变量Define SRVROOT "F:\www\ ...

  7. 网络流之最大流Dinic --- poj 1459

    题目链接 Description A power network consists of nodes (power stations, consumers and dispatchers) conne ...

  8. Failed to register dubbo:

    无法把dubbo注册到zookeeper,我的错误原因是引入的curator的版本过高,curator中会引入zookeeper,而dubbo的版本又过低,所以无法注册进zookeeper,把cura ...

  9. 【电脑】分屏显示输入信号超出范围调整为XXXXXXX

    选提示的那个范围就OK了. 注意:1.修改的是外界显示器,不是主显示器/笔记本.     2.修改外接显示器,不影响主显示器/笔记本

  10. $O(k^2)$ 求前缀 $k$ 次幂和(与长度无关)

    接下来求解前缀幂次和 求解 \(\sum_{i = 1}^{k} i^k\) \[ \begin{aligned} (p+1)^k - 1 = (p+1)^k - p^k + p^k - (p-1)^ ...