C# Entity Framework 为多个数据库事务使用存储库和工作单元模式

C# Entity Framework Using the Repository and Unit of Work Pattern for Multiple DB Transactions

我定义了三个table,PartsModelsPartModel。 对于每个 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 ModelPartModel.

中删除

目前,我的代码如下(这行得通,但请注意,我正在提交两个存储库调用,这会导致一个事务可能失败,但仍会将对数据库的更改推送到另一个存储库呼叫):

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 约束违反。

我知道我正在删除 PartModelModel,但似乎不能将两个删除操作合并到一个事务操作中。

感谢任何意见。

更新

正如一些人在这里发布的那样,我已经更新了我的代码以使用 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 假设它需要发出比实际需要更少的删除查询。