简体   繁体   English

使用存储库和工作单元模式进行多个DB事务的C#实体框架

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

I have defined three tables, Parts , Models and PartModel . 我定义了三个表PartsModelsPartModel For each Part row, there may be multiple Models row defined, and they are joined in a relationship via the PartModel table. 对于每个“ Part行,可能定义了多个“ Models行,并且它们通过“ PartModel表以关系形式连接。

Parts 部分

ID  PartName
1   Motorcycle
2   Cars

Models 楷模

ID  ModelName
1   Suzuki
2   Yamaha
3   Toyota
4   Nissan

PartModel PartModel

ID    PartID         ModelID
1       1               1
2       1               2
3       2               3
4       2               4

C# Models 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; }
    }
}

And here are my repository classes: 这是我的存储库类:

BaseRepository.cs 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 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 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);
        }
    }
}

PartsRepository.cs PartsRepository.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) { }
    }
}

ModelsRepository.cs ModelsRepository.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 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;
        }
    }
}

What I need to do is to use the repository to say, for example, I wanted to delete the model 1 (Suzuki), I can perform multiple table delete in a single transaction so that I can be assured that the data will be deleted from both tables Model and PartModel . 我需要做的是使用存储库说,例如,我想删除模型1(Suzuki),我可以在单个事务中执行多表删除,这样可以确保从中删除数据。这两个表都是ModelPartModel

Currently, my codes are as follows (this works but notice that I am committing on both Repository calls, which then has the tendency where one transaction may fail but still the changes to the database would be pushed on the other repository call): 当前,我的代码如下(此方法有效,但请注意,我在两个存储库调用上都进行了提交,这有一种趋势,即一个事务可能会失败,但对数据库的更改仍将推送到另一个存储库调用上):

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();
    }
}

How would I be able to translate this so that I can have a single Commit for both DB delete commands? 我将如何翻译它,以便对两个数据库删除命令都拥有一个Commit

I tried the following: 我尝试了以下方法:

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();
        }
    }
}

But it is throwing an error saying something about FK Constraint Violation. 但这引发了错误,说明了有关FK约束违规的问题。

I do understand that I am deleting the PartModel and the Model , but it seems it can't take both delete actions into a single transaction operation. 我确实知道我要删除PartModelModel ,但是似乎不能将两个delete动作都纳入单个事务操作中。

Appreciate any input. 感谢任何输入。

UPDATE UPDATE

As some people have posted here, I have updated my code to utilize Transactions as recommended by Microsoft . 正如某些人在此处发布的那样,我已经更新了代码,以利用Microsoft推荐的Transactions。

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;
    }

I traced the database call using MS SQL Profiler and the commands being issued are correct. 我使用MS SQL Profiler跟踪了数据库调用,并且发出的命令是正确的。 The issue I am having is that it seems that the changes are not being persisted to the database. 我遇到的问题是,更改似乎没有持久保存到数据库中。

I used a try catch block on the above code, but no error is being thrown, hence, I know that the operation is ok. 我在上面的代码上使用了try catch块,但是没有抛出任何错误,因此,我知道该操作是可以的。

Any ideas on where to look further? 关于进一步寻找的任何想法?

DbContext is aware of ambient transactions. DbContext知道环境事务。

To do show, reference System.Transactions in your project and enclose all the actions inside a block like this, which instances a TrasnactionScope : 要显示,请在您的项目中引用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();
}

If there is an exception, or you don't call ts.Complete() or you explicitly call ts.Dispose() the transaction, which means all the operations inside the block, is rolled back. 如果存在异常,或者您不调用ts.Complete()或显式调用ts.Dispose()则事务(这意味着块内的所有操作)都将回滚。

For example, you can modify your public bool DeleteModel(int ModelID) including a using that wraps all the operations, and they'll share the same transaction. 例如,您可以修改public bool DeleteModel(int ModelID)其中包括包装所有操作的using ,它们将共享同一事务。

Update: 更新:

Just to add more to this answer, since I am mostly performing data related changes, I have opted to use Database.BeginTransaction . 只是为了增加更多答案,由于我主要是在执行与数据相关的更改,因此我选择使用Database.BeginTransaction

You can use .Net's TransactionScope class to do a Multi-database transaction. 您可以使用.Net的TransactionScope类来执行多数据库事务。 You'll still have to call SaveChanges() on each context separately within the transaction, but calling Commit() on the outer TransactionScope will do the final commit. 您仍然必须在事务中的每个上下文上分别调用SaveChanges(),但是在外部TransactionScope上调用Commit()将执行最终提交。

As far as your fk violation, I'd recommend using SqlServer profiler or a DB interceptor to trace the queries EF is executing. 至于您的fk违规,我建议使用SqlServer事件探查器或数据库拦截器来跟踪EF正在执行的查询。 Sometimes you need to be more explicit in telling EF to delete dependencies: the trace should make it easy to tell what is t being deleted. 有时,您需要在告诉EF删除依赖项时更加明确:跟踪应该使您很容易知道要删除的内容。 Another thing to watch out for is that EF assumes FK relationships are Cascade On Delete by default. 还要注意的另一件事是,EF假定FK关系默认为Cascade On Delete。 If you haven't disabled this in your configuration and EF is not the one generating your DB, then the mismatch can cause EF to assume it needs to issue fewer delete queries than is actually required. 如果您尚未在配置中禁用此功能,并且EF不是生成数据库的用户,则不匹配会导致EF假定它需要发出的删除查询少于实际需要的删除查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 实体框架,存储库模式,工作单元和测试 - Entity Framework, Repository Pattern, Unit of Work and Testing 没有实体框架的 C# 中的存储库模式 - Repository pattern in C# without Entity Framework C#实体框架和存储库模式 - C# Entity Framework and Repository Pattern 存储库模式和工作单元项目中的域实体和数据实体之间的c#映射 - c# mapping between Domain Entity and Data Entity in Repository Pattern and unit of work project 使用Unity for Work of Unit / Repository模式创建Entity Framework对象 - Creating Entity Framework objects with Unity for Unit of Work/Repository pattern 使用Entity Framework 5和存储库模式和工作单元过滤内部集合 - Filtering inner collection with Entity Framework 5 and Repository pattern and Unit of Work 使用实体框架和存储库模式进行多次包含 - Multiple includes using Entity Framework and Repository Pattern 单元测试使用Moq的通用工作单元和存储库模式框架 - Unit Testing Generic Unit of Work and Repository Pattern framework using Moq 使用Moq进行单元测试的工作单元和通用存储库模式框架 - Unit Testing Unit of Work and Generic Repository Pattern framework using Moq 实体框架+存储库+工作单元 - Entity Framework + Repository + Unit of Work
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM