C# Entity Framework 为多个数据库事务使用存储库和工作单元模式
C# Entity Framework Using the Repository and Unit of Work Pattern for Multiple DB Transactions
我定义了三个table,Parts
,Models
和PartModel
。
对于每个 Part
行,可能定义了多个 Models
行,它们通过 PartModel
table.
连接成一个关系
零件
ID PartName
1 Motorcycle
2 Cars
型号
ID ModelName
1 Suzuki
2 Yamaha
3 Toyota
4 Nissan
零件模型
ID PartID ModelID
1 1 1
2 1 2
3 2 3
4 2 4
C# 模型
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
namespace TestApplication.Model
{
[Table("Parts")]
public partial class Parts
{
[Key]
public int PartID { get; set; }
[Required]
[StringLength(50)]
public string PartName { get; set; }
}
[Table("Models")]
public partial class Models
{
[Key]
public int ModelID { get; set; }
[Required]
[StringLength(50)]
public string ModelName { get; set; }
}
[Table("PartModel")]
public partial class PartModel
{
[Key]
public int PartModelID { get; set; }
public int PartID { get; set; }
public int ModelID { get; set; }
}
}
这是我的存储库 类:
BaseRepository.cs
using System;
using System.Transactions;
namespace TestApplication.Data
{
public abstract class BaseRepository<TContext> : IDisposable
where TContext : class, new()
{
private readonly bool startedNewUnitOfWork;
private readonly IUnitOfWork<TContext> unitOfWork;
private readonly TContext context;
private bool disposed;
public BaseRepository()
: this(null)
{
}
public BaseRepository(IUnitOfWork<TContext> unitOfWork)
{
if (unitOfWork == null)
{
this.startedNewUnitOfWork = true;
this.unitOfWork = BeginUnitOfWork();
}
else
{
this.startedNewUnitOfWork = false;
this.unitOfWork = unitOfWork;
}
this.context = this.unitOfWork.Context;
this.disposed = false;
}
~BaseRepository()
{
this.Dispose(false);
}
protected TContext Context
{
get
{
return this.context;
}
}
public IUnitOfWork<TContext> UnitOfWork
{
get
{
return this.unitOfWork;
}
}
public void Commit()
{
this.unitOfWork.Commit();
}
public void Rollback()
{
this.unitOfWork.Rollback();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
DisposeManagedResources();
}
this.disposed = true;
}
}
protected virtual void DisposeManagedResources()
{
if (this.startedNewUnitOfWork && (this.unitOfWork != null))
{
this.unitOfWork.Dispose();
}
}
public IUnitOfWork<TContext> BeginUnitOfWork()
{
return BeginUnitOfWork(IsolationLevel.ReadCommitted);
}
public static IUnitOfWork<TContext> BeginUnitOfWork(IsolationLevel isolationLevel)
{
return new UnitOfWorkFactory<TContext>().Create(isolationLevel);
}
}
}
IRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace TestApplication.Data
{
public interface IRepository<TContext, TEntity, TKey>
where TContext : class
where TEntity : class
{
IUnitOfWork<TContext> UnitOfWork { get; }
List<TEntity> Find(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderByMethod = null, string includePaths = "");
TEntity FindByID(TKey id);
List<TEntity> FindAll();
void Add(TEntity entity, Guid userId);
void Update(TEntity entity, Guid userId);
void Remove(TKey id, Guid userId);
void Remove(TEntity entity, Guid userId);
}
}
Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace TestApplication.Data
{
public class Repository<TContext, TEntity, TKey> : BaseRepository<TContext>, IRepository<TContext, TEntity, TKey>
where TContext : class, new()
where TEntity : class
{
private readonly DbSet<TEntity> _dbSet;
private DbContext CurrentDbContext
{
get
{
return Context as DbContext;
}
}
public Repository()
: this(null)
{
}
public Repository(IUnitOfWork<TContext> unitOfWork)
: base(unitOfWork)
{
_dbSet = CurrentDbContext.Set<TEntity>();
}
public virtual List<TEntity> Find(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderByMethod = null, string includePaths = "")
{
IQueryable<TEntity> query = _dbSet;
if (predicate != null)
query = query.Where(predicate);
if (includePaths != null)
{
var paths = includePaths.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
query = paths.Aggregate(query, (current, path) => current.Include(path));
}
var entities = orderByMethod == null ? query.ToList() : orderByMethod(query).ToList();
return entities;
}
public virtual TEntity FindByID(TKey id)
{
var entity = _dbSet.Find(id);
return entity;
}
public virtual List<TEntity> FindAll()
{
return Find();
}
public virtual void Add(TEntity entity, Guid userId)
{
if (entity == null)
throw new ArgumentNullException("entity");
_dbSet.Add(entity);
}
public virtual void Update(TEntity entity, Guid userId)
{
if (entity == null) throw new ArgumentNullException("entity");
_dbSet.Attach(entity);
CurrentDbContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Remove(TKey id, Guid userId)
{
var entity = _dbSet.Find(id);
Remove(entity, userId);
}
public virtual void Remove(TEntity entity, Guid userId)
{
if (entity == null)
throw new ArgumentNullException("entity");
if (CurrentDbContext.Entry(entity).State == EntityState.Detached)
_dbSet.Attach(entity);
_dbSet.Remove(entity);
}
}
}
部件Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class PartsRepository : Repository<DBContext, Parts, int>
{
public PartsRepository() : this(null) { }
public PartsRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
}
}
型号Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class ModelsRepository : Repository<DBContext, Models, int>
{
public ModelsRepository() : this(null) { }
public ModelsRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
}
}
PartModelRepository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class PartModelRepository : Repository<DBContext, PartModel, int>
{
public PartModelRepository() : this(null) { }
public PartModelRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
public PartModel GetPartModelByModelID(ModelID)
{
// LINQ Query here to get the PartModel object based on the ModelID
var q = (from a in this.Context.PartModel.Where(a => a.ModelID == ModelID)
select a);
PartModel partModel = q.FirstOrDefault();
return partModel;
}
}
}
我需要做的是用repository说,比如我想删除模型1(Suzuki),我可以在一个事务中执行多个table删除,这样我就可以请放心,数据将从 tables Model
和 PartModel
.
中删除
目前,我的代码如下(这行得通,但请注意,我正在提交两个存储库调用,这会导致一个事务可能失败,但仍会将对数据库的更改推送到另一个存储库呼叫):
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using(PartModelRepository partModelRepo = new PartModelRepository())
{
PartModel partModel = partModelRepo.GetPartModelByModelID(ModelID);
partModelRepo.Remove(partModel, Guid.NewGuid());
partModel.Commit();
}
// Delete the Model
using(ModelsRepository modelRepo = new ModelsRepository())
{
Models model = modelRepo.FindByID(ModelID);
modelRepo.Remove(model, Guid.NewGuid());
modelRepo.Commit();
}
}
我怎样才能翻译这个,以便我可以有一个 Commit
用于两个数据库删除命令?
我尝试了以下方法:
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using(ModelsRepository modelsRepo = new ModelsRepository())
{
using(PartModelRepository partModelRepo = new PartModelRepository(modelsRepo.UnitOfWork))
{
PartModel partModel = partModelRepo.GetPartModelByModelID(ModelID);
partModelRepo.Remove(partModel, Guid.NewGuid());
Models model = modelRepo.FindByID(ModelID);
modelRepo.Remove(model, Guid.NewGuid());
modelRepo.Commit();
}
}
}
但是它抛出一个错误,说明 FK 约束违反。
我知道我正在删除 PartModel
和 Model
,但似乎不能将两个删除操作合并到一个事务操作中。
感谢任何意见。
更新
正如一些人在这里发布的那样,我已经更新了我的代码以使用 Microsoft 推荐的交易。
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using (var transaction = this.Context.Database.BeginTransaction())
{
PartModel partModel = this.Context.PartModel.First(s => s.ModelID == ModelID);
Models model = this.Context.Models.First(s => s.ModelID == ModelID);
this.Context.PartModel.Remove(partModel);
this.Context.Models.Remove(model);
this.Context.SaveChanges();
transaction.Commit();
}
return true;
}
我使用 MS SQL Profiler 跟踪了数据库调用,发出的命令是正确的。我遇到的问题是更改似乎没有保存到数据库中。
我在上面的代码中使用了 try catch 块,但没有抛出任何错误,因此,我知道操作没问题。
关于在哪里进一步观察有什么想法吗?
DbContext
了解环境事务。
要展示,请在您的项目中引用 System.Transactions
并将所有操作包含在这样的块中,实例为 TrasnactionScope:
using(var ts = new TransactinScope())
{
// inside transaction scope
// run all the transactional operations here
// call .Complete to "commit" them all
ts.Complete();
}
如果出现异常,或者你没有调用ts.Complete()
或者你显式调用ts.Dispose()
事务,这意味着块内的所有操作都会被回滚。
例如,您可以修改 public bool DeleteModel(int ModelID)
,包括包装所有操作的 using
,它们将共享同一事务。
更新:
只是为了给这个答案添加更多内容,因为我主要执行与数据相关的更改,所以我选择使用 Database.BeginTransaction。
您可以使用 .Net 的 TransactionScope class 进行多数据库事务。您仍然需要在事务中分别对每个上下文调用 SaveChanges(),但在外部 TransactionScope 上调用 Commit() 将完成最终提交。
就您的 fk 违规而言,我建议使用 SqlServer 探查器或数据库拦截器来跟踪 EF 正在执行的查询。有时您需要更明确地告诉 EF 删除依赖项:跟踪应该可以很容易地分辨出哪些内容没有被删除。另一件需要注意的事情是,EF 假定 FK 关系在默认情况下是 Cascade On Delete。如果您没有在您的配置中禁用它并且 EF 不是生成您的数据库的那个,那么不匹配会导致 EF 假设它需要发出比实际需要更少的删除查询。
我定义了三个table,Parts
,Models
和PartModel
。
对于每个 Part
行,可能定义了多个 Models
行,它们通过 PartModel
table.
零件
ID PartName
1 Motorcycle
2 Cars
型号
ID ModelName
1 Suzuki
2 Yamaha
3 Toyota
4 Nissan
零件模型
ID PartID ModelID
1 1 1
2 1 2
3 2 3
4 2 4
C# 模型
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
namespace TestApplication.Model
{
[Table("Parts")]
public partial class Parts
{
[Key]
public int PartID { get; set; }
[Required]
[StringLength(50)]
public string PartName { get; set; }
}
[Table("Models")]
public partial class Models
{
[Key]
public int ModelID { get; set; }
[Required]
[StringLength(50)]
public string ModelName { get; set; }
}
[Table("PartModel")]
public partial class PartModel
{
[Key]
public int PartModelID { get; set; }
public int PartID { get; set; }
public int ModelID { get; set; }
}
}
这是我的存储库 类:
BaseRepository.cs
using System;
using System.Transactions;
namespace TestApplication.Data
{
public abstract class BaseRepository<TContext> : IDisposable
where TContext : class, new()
{
private readonly bool startedNewUnitOfWork;
private readonly IUnitOfWork<TContext> unitOfWork;
private readonly TContext context;
private bool disposed;
public BaseRepository()
: this(null)
{
}
public BaseRepository(IUnitOfWork<TContext> unitOfWork)
{
if (unitOfWork == null)
{
this.startedNewUnitOfWork = true;
this.unitOfWork = BeginUnitOfWork();
}
else
{
this.startedNewUnitOfWork = false;
this.unitOfWork = unitOfWork;
}
this.context = this.unitOfWork.Context;
this.disposed = false;
}
~BaseRepository()
{
this.Dispose(false);
}
protected TContext Context
{
get
{
return this.context;
}
}
public IUnitOfWork<TContext> UnitOfWork
{
get
{
return this.unitOfWork;
}
}
public void Commit()
{
this.unitOfWork.Commit();
}
public void Rollback()
{
this.unitOfWork.Rollback();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
DisposeManagedResources();
}
this.disposed = true;
}
}
protected virtual void DisposeManagedResources()
{
if (this.startedNewUnitOfWork && (this.unitOfWork != null))
{
this.unitOfWork.Dispose();
}
}
public IUnitOfWork<TContext> BeginUnitOfWork()
{
return BeginUnitOfWork(IsolationLevel.ReadCommitted);
}
public static IUnitOfWork<TContext> BeginUnitOfWork(IsolationLevel isolationLevel)
{
return new UnitOfWorkFactory<TContext>().Create(isolationLevel);
}
}
}
IRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace TestApplication.Data
{
public interface IRepository<TContext, TEntity, TKey>
where TContext : class
where TEntity : class
{
IUnitOfWork<TContext> UnitOfWork { get; }
List<TEntity> Find(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderByMethod = null, string includePaths = "");
TEntity FindByID(TKey id);
List<TEntity> FindAll();
void Add(TEntity entity, Guid userId);
void Update(TEntity entity, Guid userId);
void Remove(TKey id, Guid userId);
void Remove(TEntity entity, Guid userId);
}
}
Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace TestApplication.Data
{
public class Repository<TContext, TEntity, TKey> : BaseRepository<TContext>, IRepository<TContext, TEntity, TKey>
where TContext : class, new()
where TEntity : class
{
private readonly DbSet<TEntity> _dbSet;
private DbContext CurrentDbContext
{
get
{
return Context as DbContext;
}
}
public Repository()
: this(null)
{
}
public Repository(IUnitOfWork<TContext> unitOfWork)
: base(unitOfWork)
{
_dbSet = CurrentDbContext.Set<TEntity>();
}
public virtual List<TEntity> Find(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderByMethod = null, string includePaths = "")
{
IQueryable<TEntity> query = _dbSet;
if (predicate != null)
query = query.Where(predicate);
if (includePaths != null)
{
var paths = includePaths.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
query = paths.Aggregate(query, (current, path) => current.Include(path));
}
var entities = orderByMethod == null ? query.ToList() : orderByMethod(query).ToList();
return entities;
}
public virtual TEntity FindByID(TKey id)
{
var entity = _dbSet.Find(id);
return entity;
}
public virtual List<TEntity> FindAll()
{
return Find();
}
public virtual void Add(TEntity entity, Guid userId)
{
if (entity == null)
throw new ArgumentNullException("entity");
_dbSet.Add(entity);
}
public virtual void Update(TEntity entity, Guid userId)
{
if (entity == null) throw new ArgumentNullException("entity");
_dbSet.Attach(entity);
CurrentDbContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Remove(TKey id, Guid userId)
{
var entity = _dbSet.Find(id);
Remove(entity, userId);
}
public virtual void Remove(TEntity entity, Guid userId)
{
if (entity == null)
throw new ArgumentNullException("entity");
if (CurrentDbContext.Entry(entity).State == EntityState.Detached)
_dbSet.Attach(entity);
_dbSet.Remove(entity);
}
}
}
部件Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class PartsRepository : Repository<DBContext, Parts, int>
{
public PartsRepository() : this(null) { }
public PartsRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
}
}
型号Repository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class ModelsRepository : Repository<DBContext, Models, int>
{
public ModelsRepository() : this(null) { }
public ModelsRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
}
}
PartModelRepository.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TestApplication.Model;
namespace TestApplication.Data
{
public class PartModelRepository : Repository<DBContext, PartModel, int>
{
public PartModelRepository() : this(null) { }
public PartModelRepository(IUnitOfWork<DBContext> unitOfWork) : base(unitOfWork) { }
public PartModel GetPartModelByModelID(ModelID)
{
// LINQ Query here to get the PartModel object based on the ModelID
var q = (from a in this.Context.PartModel.Where(a => a.ModelID == ModelID)
select a);
PartModel partModel = q.FirstOrDefault();
return partModel;
}
}
}
我需要做的是用repository说,比如我想删除模型1(Suzuki),我可以在一个事务中执行多个table删除,这样我就可以请放心,数据将从 tables Model
和 PartModel
.
目前,我的代码如下(这行得通,但请注意,我正在提交两个存储库调用,这会导致一个事务可能失败,但仍会将对数据库的更改推送到另一个存储库呼叫):
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using(PartModelRepository partModelRepo = new PartModelRepository())
{
PartModel partModel = partModelRepo.GetPartModelByModelID(ModelID);
partModelRepo.Remove(partModel, Guid.NewGuid());
partModel.Commit();
}
// Delete the Model
using(ModelsRepository modelRepo = new ModelsRepository())
{
Models model = modelRepo.FindByID(ModelID);
modelRepo.Remove(model, Guid.NewGuid());
modelRepo.Commit();
}
}
我怎样才能翻译这个,以便我可以有一个 Commit
用于两个数据库删除命令?
我尝试了以下方法:
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using(ModelsRepository modelsRepo = new ModelsRepository())
{
using(PartModelRepository partModelRepo = new PartModelRepository(modelsRepo.UnitOfWork))
{
PartModel partModel = partModelRepo.GetPartModelByModelID(ModelID);
partModelRepo.Remove(partModel, Guid.NewGuid());
Models model = modelRepo.FindByID(ModelID);
modelRepo.Remove(model, Guid.NewGuid());
modelRepo.Commit();
}
}
}
但是它抛出一个错误,说明 FK 约束违反。
我知道我正在删除 PartModel
和 Model
,但似乎不能将两个删除操作合并到一个事务操作中。
感谢任何意见。
更新
正如一些人在这里发布的那样,我已经更新了我的代码以使用 Microsoft 推荐的交易。
public bool DeleteModel(int ModelID)
{
// Get the PartModel based on the ModelID
using (var transaction = this.Context.Database.BeginTransaction())
{
PartModel partModel = this.Context.PartModel.First(s => s.ModelID == ModelID);
Models model = this.Context.Models.First(s => s.ModelID == ModelID);
this.Context.PartModel.Remove(partModel);
this.Context.Models.Remove(model);
this.Context.SaveChanges();
transaction.Commit();
}
return true;
}
我使用 MS SQL Profiler 跟踪了数据库调用,发出的命令是正确的。我遇到的问题是更改似乎没有保存到数据库中。
我在上面的代码中使用了 try catch 块,但没有抛出任何错误,因此,我知道操作没问题。
关于在哪里进一步观察有什么想法吗?
DbContext
了解环境事务。
要展示,请在您的项目中引用 System.Transactions
并将所有操作包含在这样的块中,实例为 TrasnactionScope:
using(var ts = new TransactinScope())
{
// inside transaction scope
// run all the transactional operations here
// call .Complete to "commit" them all
ts.Complete();
}
如果出现异常,或者你没有调用ts.Complete()
或者你显式调用ts.Dispose()
事务,这意味着块内的所有操作都会被回滚。
例如,您可以修改 public bool DeleteModel(int ModelID)
,包括包装所有操作的 using
,它们将共享同一事务。
更新:
只是为了给这个答案添加更多内容,因为我主要执行与数据相关的更改,所以我选择使用 Database.BeginTransaction。
您可以使用 .Net 的 TransactionScope class 进行多数据库事务。您仍然需要在事务中分别对每个上下文调用 SaveChanges(),但在外部 TransactionScope 上调用 Commit() 将完成最终提交。
就您的 fk 违规而言,我建议使用 SqlServer 探查器或数据库拦截器来跟踪 EF 正在执行的查询。有时您需要更明确地告诉 EF 删除依赖项:跟踪应该可以很容易地分辨出哪些内容没有被删除。另一件需要注意的事情是,EF 假定 FK 关系在默认情况下是 Cascade On Delete。如果您没有在您的配置中禁用它并且 EF 不是生成您的数据库的那个,那么不匹配会导致 EF 假设它需要发出比实际需要更少的删除查询。