EF5中使用UnitOfWork

一、前言

      每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。解决办法是:在ObjectContext的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。同一个UnitOfWork实例对象下所有的ObjectContext都共同一个数据库上下文对象(ps:EF用的是ObjectContext),也就是共用一个事物。提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。

当前页面:https://www.cnblogs.com/masonblog/p/9801162.html

代码托管:https://github.com/catbiscuit/MT

二、实现

1、MT.Business.Model

(1)项目截图

2、MT.Data.Base 基础数据库层

 (1)项目截图

 

(2)详细代码在GitHub上下载代码查看,贴出来篇幅过多。

 3、使用。以T_Log表为例,实体类为T_Log.cs。

(1)IDAL层。

直接继承IRepository<T>泛型接口,其中定义了一些基础的方法。

namespace MT.Business.IDAL.SystemManage
{
    public interface IT_LogDAL : IRepository<T_Log>
    {
    }
}

(2)DAL层

直接继承Repository<T>类,和IT_LogDAL接口。

Repository类,需要提供实体类和数据库上下文。因为需要创建一个统一管理数据库操作的对象。

namespace MT.Business.DAL.SystemManage
{
    public class T_LogDAL : Repository<T_Log, MTEntities>, IT_LogDAL
    {
        public T_LogDAL(IDatabaseFactory<MTEntities> iDatabaseFactory)
            : base(iDatabaseFactory)
        {

        }
    }
}

(3)IBLL层。

定义基本的数据操作方法。

namespace MT.Business.IBLL.SystemManage
{
    public interface IT_LogBLL
    {
        int Insert(T_Log entity);
        int Delete(T_Log entity);
        int Update();
        int Insert(List<T_Log> lstEntity);

        T_Log GetModelByCondition(Expression<Func<T_Log, bool>> condition);
    }
}

(4)BLL层。

IT_LogBLL接口中方法的实现。

 

namespace MT.Business.BLL.SystemManage
{
    public class T_LogBLL : IT_LogBLL
    {
        private IUnitOfWork<MTEntities> _iUnitOfWork;
        private IT_LogDAL _iT_LogDAL;

        public T_LogBLL(IUnitOfWork<MTEntities> iUnitOfWork
            , IT_LogDAL iT_LogDAL)
        {
            this._iUnitOfWork = iUnitOfWork;
            this._iT_LogDAL = iT_LogDAL;
        }

        public int Insert(T_Log entity)
        {
            _iT_LogDAL.Insert(entity);
            return _iUnitOfWork.Commit();
        }
        public int Delete(T_Log entity)
        {
            _iT_LogDAL.Delete(entity);
            return _iUnitOfWork.Commit();
        }

        public int Update()
        {
            return _iUnitOfWork.Commit();
        }

        public int Insert(List<T_Log> lstEntity)
        {
            foreach (var item in lstEntity)
            {
                _iT_LogDAL.Insert(item);
            }
            return _iUnitOfWork.Commit();
        }

        public T_Log GetModelByCondition(Expression<Func<T_Log, bool>> condition)
        {
            return _iT_LogDAL.GetModelByCondition(condition);
        }
    }
}

 

注意:

第一:类中定义了一个IUnitOfWork泛型对象,这个就是单元事务的管理对象。

DAL层只是将对数据的操作,添加到一个数据缓存的集合中。

当数据操作的步骤完成时,才调用IUnitOfWork泛型对象进行数据操作的提交。

这样确保了原子性和一致性。

第二:Update操作,BLL并没有将数据的操作添加到数据缓存的集合中。

此处,可以通过单步调试,当执行到UnitOfWork类中时,

监视DbContext对象的值,这个对象会监视数据库上下文中数据的状态。

 

这个地方,就要求Update操作的对象只能是从数据库获取得来。

使用lambda表达式获取实体。

示例操作:

Model model =  DAL.GetModel();

model.Name = "修改Name";

BLL.Update();

此时BLL层执行update方法,实际上只是执行了单元事务的提交,也就完成了对该条记录的修改。

所以,如果是实例化的对象是无法修改数据。

Model model = new Model();

model.ID = "2";

model.Name = "修改Name";

BLL.Update();

这样执行更新操作,是无法真正修改数据的。

4、总结

(1)思路

DAL层,只是将数据的操作(新增修改删除)附加到数据库上下文中。

UnitOfWork,才是将数据进行提交的地方。

所以,BLL层的方法才能实现对数据的完成操作。

(2)使用工作单元的思路。

a.DatabaseFactory类的使用。确保一次业务中每一个线程使用的数据库上下文对象是同一个。

Web中每一次HTTP请求,都会开启一个新的线程。

我们要保证在这个线程中,所有的数据库操作都提交到了同一个数据库上下文对象中。

在一次业务操作时,所使用的数据库上下文对象都由DatabaseFactory实例化。

b.DAL层中的Repository类。将一次业务操作中的数据库实体增删改实体附加到从DatabaseFactory中获取到的数据库上下文中。

Repository类的构造函数,就要求传递数DatabaseFactory的对象。

Repository类中使用的数据库上下文对象,从DatabaseFactory获取。

c.BLL层中的UnitOfWork类。将一次业务操作中的数据库上下文操作提交到数据库中。

UnitOfWork类中使用的数据库上下文对象,从DatabaseFactory获取。

调用各个表DAL层对象,将增删改的操作附加到从DatabaseFactory类获取到的数据库上下文对象中。

最终调用UnitOfWork类的Commit();方法,将所有的数据操作提交到数据库中去。

(3)关键点

a.同一个业务操作时,不同表操作时使用的数据库上下文必须是同一个,保证数据提交的完整性。

b.将数据库附加放在DAL层,数据的提交放在BLL层。

2018-10-17 17:06:23 发现了问题。获取到的数据库上下文对象均是

三、关于每一次HTTP请求中数据库上下文对象的获取问题。

1、问题。

昨天在网上看到了,每一次HTTP请求都会开启一个新的线程,这句话不理解是什么意思,就直接照着这个思路来。

但是,总感觉有什么问题,于是今天做了测试。

在HomeController控制器中,

在Index方法中,获取实体,修改属性值,但是不调用BLL层,也就是仅附加不提交。

在Add方法中,获取实体,修改属性值,之后调用BLL层,附加且提交。

经过测试,发现了问题

执行了Index方法,F_isValid++; ,数据库中记录的值并不会改变。

但是当执行了Add方法后,F_isValid++;,数据库中记录的值会发生改变,但是F_isValid自增了两次。

有问题,然后继续测试。执行了三次Index方法,再执行一次Add方法,数据库中记录的值自增了四次。

调试发现,自从程序运行后,DatabaseFactory类中的private T _dbcontext; 一直存在。也就是整个程序周期内,这个示例是共享的。

于是有两个问题。

(1)在一个HTTP周期内,使用的数据库上下文对象应该是同一个。

(2)在一个HTTP周期结束后,使用的数据库上下文对象应该被销毁(回收)。

public class HomeController : Controller
    {
        private readonly IT_LogBLL _iT_LogBLL;

        public HomeController(IT_LogBLL iT_LogBLL)
        {
            this._iT_LogBLL = iT_LogBLL;
        }

        public ActionResult Index()
        {
            T_Log model2 = new T_Log();
            model2 = _iT_LogBLL.GetModelByCondition(x => x.F_isValid > 0);
            model2.F_isValid++;
            //_iT_LogBLL.Update();

            return View();
        }

        public ActionResult Add()
        {
            T_Log model2 = new T_Log();
            model2 = _iT_LogBLL.GetModelByCondition(x => x.F_isValid > 0);
            model2.F_isValid++;
            _iT_LogBLL.Update();

            return View();
        }
    }
View Code

 

2、解决

(1)在一个HTTP周期内,使用的数据库上下文对象应该是同一个。---Autofac配置格式

想起Autofac中,有实例生命周期的设置。

地址:https://www.cnblogs.com/masonblog/p/9584299.html 

又寻找到配置格式:

builder.RegisterType<OpenIdRelyingPartyService>().As<IOpenIdRelyingPartyService>().InstancePerHttpRequest();

基于线程或者Context上下文的请求,返回一个单例实例,在Controller的一个View页面执行时包含了整个Context上下文处理.

地址:https://www.cnblogs.com/xiaoweinet/archive/2014/01/09/3512255.html

(2)在一个HTTP周期结束后,使用的数据库上下文对象应该被销毁(回收)。---IDisposable回收

参照网上大神的设置,对DatabaseFactory 资源的回收。

地址:https://www.cnblogs.com/tiancai/p/6612444.html

3、测试

(1)、InstancePerDependency
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerDependency();
每次获取DatabaseFactory类都是新的实体,
数据无法添加。

(2)、InstancePerLifetimeScope
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerLifetimeScope();
每次获取DatabaseFactory类都是新的实体,
数据无法添加。

(3)、InstancePerMatchingLifetimeScope
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerMatchingLifetimeScope();
No scope with a Tag matching '' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
报错,程序无法运行。

(4)、InstancePerOwned
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerOwned(typeof(DatabaseFactory<>));
No scope with a Tag matching 'MT.Data.Base.BaseObject.DatabaseFactory`1' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
报错,程序无法运行。

(5)、SingleInstance
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).SingleInstance();
单例模式,执行时得到的都是同一个相同的共享的实例。
一个HTTP请求,将实体附加到数据库上下问中,但是不提交。在下一个HTTP请求中,得到的数据库上下文对象与上一次HTTP请求的数据库上下文对象是同一个。
数据会将之前附加未提交的记录一并提交。
数据添加异常,不能这样操作。

(6)、InstancePerHttpRequest
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerRequest();
builder.RegisterType<DatabaseFactory<MTEntities>>().As<IDatabaseFactory<MTEntities>>().InstancePerRequest();
在一次HTTP请求中,获取到的数据库上下文中对象是同一个。
数据正常添加。

4、最终设置格式

修改或添加的文件:AutofacRegister,DatabaseFactory,Disposable,IDatabaseFactory

namespace MT.Application.Web.App_Code
{
    /// <summary>
    /// Autofac注册
    /// </summary>
    public class AutofacRegister
    {
        public static ContainerBuilder Register()
        {
            ContainerBuilder builder = null;
            try
            {
                builder = new ContainerBuilder();
                //工作单元
                builder.RegisterGeneric(typeof(UnitOfWork<>)).As(typeof(IUnitOfWork<>));//单例模式
                //数据库工厂                
                builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerRequest();

                //T_Log类
                builder.RegisterType<T_LogDAL>().As<IT_LogDAL>();
                builder.RegisterType<T_LogBLL>().As<IT_LogBLL>();

                return builder;
            }
            catch
            {
                builder = new ContainerBuilder();
                return builder;
            }
        }

    }
}

namespace MT.Data.Base.BaseObject
{
    /// <summary>
    /// 数据库工厂类
    /// </summary>
    public class DatabaseFactory<T> : Disposable, IDatabaseFactory<T>
        where T : class
    {
        private T _dbcontext;
        /// <summary>
        /// 获取一个数据库上下文对象
        /// 每一次http请求都会开启一个新的线程
        /// 保证在一个线程(功能)中,DbContext是唯一
        /// </summary>
        /// <returns></returns>
        public T Get()
        {
            try
            {
                if (_dbcontext == null)
                {
                    Type t = typeof(T);
                    System.Reflection.ConstructorInfo ci = t.GetConstructor(System.Type.EmptyTypes);
                    T to = (T)ci.Invoke(new object[0]);
                    _dbcontext = to;
                }
                return _dbcontext;
            }
            catch (Exception)
            {
                throw;
            }
        }

        protected override void DisposeCore()
        {
            if (_dbcontext != null)
            {
                ((IDisposable)_dbcontext).Dispose();
            }
        }
    }
}

namespace MT.Data.Base.BaseObject
{
    /// <summary>
    /// 释放资源
    /// </summary>
    public class Disposable
    {
        //是否回收完毕
        private bool isDisposed;

        ~Disposable()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            //标志位:未回收,且参数标识为需要回收资源
            if (!isDisposed && disposing)
            {
                //释放那些实现IDisposable接口的托管对象。此处调用方法进行释放。
                DisposeCore();
            }            
            isDisposed = true;
            //释放非托管资源。此处没有需要释放的非托管对象
        }

        protected virtual void DisposeCore()
        {
        }
    }
}

namespace MT.Data.Base.IBaseInterface
{
    /// <summary>
    /// 数据库工厂接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IDatabaseFactory<T> : IDisposable
        where T : class
    {
        T Get();
    }
}
View Code

 

转载于:https://www.cnblogs.com/masonblog/p/9801162.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值