zoukankan      html  css  js  c++  java
  • EF5中使用UnitOfWork

    前言

    每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。

    解决办法是:在ObjectContext的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。

    同一个UnitOfWork实例对象下所有的ObjectContext都共同一个数据库上下文对象(ps:EF用的是ObjectContext),也就是共用一个事物。

    提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。

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

    实现过程

    实体层

    数据访问层

    T_Log表示例

    IDAL层

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

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

    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)
            {
    
            }
        }
    }

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

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

    model.Name = "修改Name";

    BLL.Update();

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

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

    Model model = new Model();

    model.ID = 2;

    model.Name = "修改Name";

    BLL.Update();

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

    总结

    思路

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

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

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

    使用工作单元的思路

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

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

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

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

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

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

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

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

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

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

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

    关键点

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

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

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

    问题

    在网上看到了,每一次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();
        }
    }

    解决

    (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

    测试

    (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请求中,获取到的数据库上下文中对象是同一个。
    数据正常添加。

    最终设置格式

    修改或添加的文件: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();
        }
    }
  • 相关阅读:
    自写简单弹出框
    eclipse中debug模式不能启动运行,run运行模式却能启动运行!
    事务特性简单理解
    Java虚拟机4:内存溢出
    Java虚拟机3:常用JVM命令参数
    Java虚拟机2:Java内存区域及对象
    Eclipse技巧
    js常用的原生方法
    JavaScript调试小技巧
    html 锚点的使用
  • 原文地址:https://www.cnblogs.com/masonblog/p/9801162.html
Copyright © 2011-2022 走看看