ABP使用及框架解析系列 - [Unit of Work part.2-框架实现]

 

前言

ABP

ABP是“ASP.NET Boilerplate Project”的简称。

ABP的官方网站:http://www.aspnetboilerplate.com

ABP在Github上的开源项目:https://github.com/aspnetboilerplate

ABP其他学习博客推荐及介绍:http://www.cnblogs.com/mienreal/p/4528470.html

ABP中Unit of Work概念及使用

如果这是你首次接触ABP框架或ABP的Unit of Work,推荐先看看 ABP使用及框架解析系列-[Unit of Work part.1-概念及使用]

框架实现

温馨提示

1.ABP的Unit of Work相关代码路径为:/Abp/Domain/Uow

2.框架实现中,代码不会贴全部的,但是会说明代码在项目中的位置,并且为了更加直观和缩短篇幅,对代码更细致的注释,直接在代码中,不要忘记看代码注释哈,。

3.博客中的代码,有一些方法是可以点击的!

动态代理/拦截器/AOP

上面讲到Unit of Work有两个默认实现,领域服务和仓储库的每个方法默认就是一个工作单元,这个是如何实现的呢?在方法上添加一个UnitOfWork特性也就让该方法为一个工作单元,这又是如何实现的呢?上面的标题已然暴露了答案——动态代理

在ABP中,使用了Castle的DynamicProxy进行动态代理,在组件注册是进行拦截器的注入,具体代码如下:

internal static class UnitOfWorkRegistrar
{
public static void Initialize(IIocManager iocManager)
{//该方法会在应用程序启动的时候调用,进行事件注册
iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered;
} private static void ComponentRegistered(string key, IHandler handler)
{
if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation))
{//判断类型是否实现了IRepository或IApplicationService,如果是,则为该类型注册拦截器(UnitOfWorkInterceptor)
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute))
{//或者类型中任何一个方法上应用了UnitOfWorkAttribute,同样为类型注册拦截器(UnitOfWorkInterceptor)
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
}
public static bool IsConventionalUowClass(Type type)
{
return typeof(IRepository).IsAssignableFrom(type) || typeof(IApplicationService).IsAssignableFrom(type);
}
public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo)
{
return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true);
}

拦截器UnitOfWorkInterceptor实现了IInterceptor接口,在调用注册了拦截器的类的方法时,会被拦截下来,而去执行IInterceptor的Intercept方法,下面是Intercept方法的代码实现:

public void Intercept(IInvocation invocation)
{
if (_unitOfWorkManager.Current != null)
{//如果当前已经在工作单元中,则直接执行被拦截类的方法
invocation.Proceed();
return;
} //获取方法上的UnitOfWorkAttribute,如果没有返回NULL,invocation.MethodInvocationTarget为被拦截类的类型
var unitOfWorkAttr = UnitOfWorkAttribute.GetUnitOfWorkAttributeOrNull(invocation.MethodInvocationTarget);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{//如果当前方法上没有UnitOfWorkAttribute或者是设置为Disabled,则直接调用被拦截类的方法
invocation.Proceed();
return;
} //走到这里就表示是需要将这个方法作为工作单元了,详情点击查看
PerformUow(invocation, unitOfWorkAttr.CreateOptions());
}
internal static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MemberInfo methodInfo)
{
//获取方法上标记的UnitOfWorkAttribute
var attrs = methodInfo.GetCustomAttributes(typeof(UnitOfWorkAttribute), false);
if (attrs.Length > 0)
{
return (UnitOfWorkAttribute)attrs[0];
} if (UnitOfWorkHelper.IsConventionalUowClass(methodInfo.DeclaringType))
{//如果方法上没有标记UnitOfWorkAttribute,但是方法的所属类实现了IRepository或IApplicationService,则返回一个默认UnitOfWorkAttribute
return new UnitOfWorkAttribute();
} return null;
}
private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
{
if (AsyncHelper.IsAsyncMethod(invocation.Method))
{//被拦截的方法为异步方法
PerformAsyncUow(invocation, options);
}
else
{//被拦截的方法为同步方法
PerformSyncUow(invocation, options);
}
}
private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
//手动创建一个工作单元,将被拦截的方法直接放在工作单元中
using (var uow = _unitOfWorkManager.Begin(options))
{
invocation.Proceed();
uow.Complete();
}
}
private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
//异步方法的处理相对麻烦,需要将工作单元的Complete和Dispose放到异步任务中
var uow = _unitOfWorkManager.Begin(options); invocation.Proceed(); if (invocation.Method.ReturnType == typeof(Task))
{//如果是无返回值的异步任务
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task)invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
else //Task<TResult>
{//如果是有返回值的异步任务
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[0],
invocation.ReturnValue,
async () => await uow.CompleteAsync(),
(exception) => uow.Dispose()
);
}
}
/// <summary>
/// 修改异步返回结果,并且使用try...catch...finally
/// </summary>
/// <param name="actualReturnValue">方法原始返回结果</param>
/// <param name="postAction">期望返回结果</param>
/// <param name="finalAction">无论是否异常都将执行的代码</param>
/// <returns>新的异步结果</returns>
public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
//在方法被拦截方法执行前调用工作单元的Begin,修改异步结果为调用工作单元的CompleteAsync方法,并保证工作单元会被Dispose掉
try
{
await actualReturnValue;
await postAction();
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
{
//有返回值的异步任务重写更为复杂,需要先通过反射来为泛型传值,然后才可调用泛型方法来重写异步返回值
return typeof (InternalAsyncHelper)
.GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(taskReturnType)
.Invoke(null, new object[] { actualReturnValue, action, finalAction });
}
public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
//该方法与之前无返回值的异步任务调用的方法相同,只是多了个泛型
try
{
var result = await actualReturnValue;
await postAction();
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}

总结来说,就是通过拦截器在执行方法的时候,先判断是否需要进行工作单元操作。如果需要,则在执行方法前开启工作单元,在执行方法后关闭工作单元。

在上面的代码中,我们可以看到,工作单元都是通过_unitOfWorkManager(IUnitOfWorkManager)这样一个对象进行的,下面我们就来解析这个类到底是如何进行单元控制的。

IUnitOfWorkManager、IUnitOfWorkCompleteHandle

public interface IUnitOfWorkManager
{
IActiveUnitOfWork Current { get; }
IUnitOfWorkCompleteHandle Begin();
IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope);
IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options);
}

ABP中,默认将UnitOfWorkManager作为IUnitOfWorkManager作为实现类,其实现中,Current直接取得ICurrentUnitOfWorkProvider对象的Current属性,后续解析ICurrentUnitOfWorkProvider。而IUnitOfWorkManager的三个Begin只是重载,最后都将调用第三个Begin的重载方法。下面是它的代码实现:

public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
//为未赋值的参数设置默认值
options.FillDefaultsForNonProvidedOptions(_defaultOptions); if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null)
{//如果当前Scope的设置为Required(而非RequiredNew),并且当前已存在工作单元,那么久返回下面这样的一个对象
return new InnerUnitOfWorkCompleteHandle();
}
//走到这里,表示需要一个新的工作单元,通过IoC创建IUnitOfWork实现对象,然后开始工作单元,并设置此工作单元为当前工作单元
var uow = _iocResolver.Resolve<IUnitOfWork>(); uow.Completed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
};
uow.Failed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
};
uow.Disposed += (sender, args) =>
{
_iocResolver.Release(uow);
}; uow.Begin(options);
_currentUnitOfWorkProvider.Current = uow; return uow;
}
public IActiveUnitOfWork Current
{
get { return _currentUnitOfWorkProvider.Current; }
}
internal void FillDefaultsForNonProvidedOptions(IUnitOfWorkDefaultOptions defaultOptions)
{
if (!IsTransactional.HasValue)
{
IsTransactional = defaultOptions.IsTransactional;
} if (!Scope.HasValue)
{
Scope = defaultOptions.Scope;
} if (!Timeout.HasValue && defaultOptions.Timeout.HasValue)
{
Timeout = defaultOptions.Timeout.Value;
} if (!IsolationLevel.HasValue && defaultOptions.IsolationLevel.HasValue)
{
IsolationLevel = defaultOptions.IsolationLevel.Value;
}
}

Begin方法最后返回的对象继承自IUnitOfWorkCompleteHandle,让我们看看IUnitOfWorkCompleteHandle的接口声明又是什么样的:

public interface IUnitOfWorkCompleteHandle : IDisposable
{
void Complete(); Task CompleteAsync();
}

总共也就两个方法,而且意思相同,都是用来完成当前工作单元的,一个同步一个异步。同时实现了IDisposable接口,结合IUnitOfWorkManager使用Begin的方式便可理解其含义(使用using)。

在之前的Begin实现中,我们看到,其返回路线有两个,一个返回了InnerUnitOfWorkCompleteHandle对象,另一个返回了IUnitOfWork实现对象。IUnitOfWork稍后详细解析,让我们先来解析InnerUnitOfWorkCompleteHandle :

internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle
{
public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work."; private volatile bool _isCompleteCalled;
private volatile bool _isDisposed; public void Complete()
{
_isCompleteCalled = true;
} public async Task CompleteAsync()
{
_isCompleteCalled = true;
} public void Dispose()
{
if (_isDisposed)
{
return;
} _isDisposed = true; if (!_isCompleteCalled)
{
if (HasException())
{
return;
} throw new AbpException(DidNotCallCompleteMethodExceptionMessage);
}
} private static bool HasException()
{
try
{
return Marshal.GetExceptionCode() != 0;
}
catch (Exception)
{
return false;
}
}
}

我们可以看到,Complete和CompleteAsync里面,都只是简单的将_isCompleteCalled设置为true,在Dispose方法中,也仅仅只是判断是否已经dispose过、是否完成、是否有异常,没有更多的动作。这样大家会不会有一个疑问“这个工作单元的完成和释放工作里没有具体的完成操作,怎么就算完成工作单元了?”,这时,让我们结合使用到InnerUnitOfWorkCompleteHandle的地方来看:

if (options.Scope == TransactionScopeOption.Required && _currentUnitOfWorkProvider.Current != null)
{
return new InnerUnitOfWorkCompleteHandle();
}

这个判断,代表了当前已经存在工作单元,所以这个就是用于工作单元嵌套。内部的工作单元在提交和释放时,不需要做实际的提交和释放,只需要保证没有异常抛出,然后最外层工作单元再进行实际的提交和释放。这也就说明,在Begin方法中的另一条路线,返回IUnitOfWork实现对象才是最外层的事务对象。

IUnitOfWork

public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle
{
//唯一的标识ID
string Id { get; } //外层工作单元
IUnitOfWork Outer { get; set; } //开始工作单元
void Begin(UnitOfWorkOptions options);
}

IUnitOfWork除了继承了IUnitOfWorkCompleteHandle接口,拥有了Complete方法外,还继承了IActiveUnitOfWork接口:

public interface IActiveUnitOfWork
{
//三个事件
event EventHandler Completed;
event EventHandler<UnitOfWorkFailedEventArgs> Failed;
event EventHandler Disposed; //工作单元设置
UnitOfWorkOptions Options { get; } //数据过滤配置
IReadOnlyList<DataFilterConfiguration> Filters { get; } bool IsDisposed { get; } void SaveChanges(); Task SaveChangesAsync(); IDisposable DisableFilter(params string[] filterNames);
IDisposable EnableFilter(params string[] filterNames);
bool IsFilterEnabled(string filterName);
IDisposable SetFilterParameter(string filterName, string parameterName, object value);
}

这个接口中包含了很多Filter相关的属性与方法,都是些数据过滤相关的,这里不对其进行介绍,所以这个接口里,主要包含了三个事件(Completed、Failed、Disposed),工作单元设置(Options),IsDisposed以及同步异步的SaveChanges。

除了IUnitOfWork接口,ABP还提供了一个实现IUnitOfWork接口的抽象基类UnitOfWorkBase,UnitOfWorkBase的主要目的,是为开发人员处理一些前置、后置工作和异常处理,所以UnitOfWorkBase实现了部分方法,并提供了一些抽象方法,在实现的部分中做好前后置工作,然后调用抽象方法,将主要实现交给子类,并对其进行异常处理,下面以Begin实现与Complete实现为例:

public void Begin(UnitOfWorkOptions options)
{
if (options == null)
{
throw new ArgumentNullException("options");
} //防止Begin被多次调用
PreventMultipleBegin();
Options = options; //过滤配置
SetFilters(options.FilterOverrides); //抽象方法,子类实现
BeginUow();
} public void Complete()
{
//防止Complete被多次调用
PreventMultipleComplete();
try
{
//抽象方法,子类实现
CompleteUow();
_succeed = true;
OnCompleted();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
private void PreventMultipleBegin()
{
if (_isBeginCalledBefore)
{//如果已经调用过Begin方法,再次调用则抛出异常
throw new AbpException("This unit of work has started before. Can not call Start method more than once.");
} _isBeginCalledBefore = true;
}
private void PreventMultipleComplete()
{
if (_isCompleteCalledBefore)
{//如果已经掉用过Complete方法,再次调用则抛出异常
throw new AbpException("Complete is called before!");
} _isCompleteCalledBefore = true;
}

UnitOfWorkBase的其他实现与上面的类似(非Unit of Work相关内容略去),便不全贴出。但是上面的代码,大家有发现一个问题吗? 就是那些PreventMultiple…方法的实现,用来防止多次Begin、Complete等。这些方法的实现,在单线程下是没有问题的,但是里面并没有加锁,所以不适用于多线程。这是作者的BUG吗? 然而,这却并不是BUG,而是设计如此,为何呢?因为在设计上,一个线程共用一个工作单元,也就不存在多线程了。关于这部分内容,后续将会介绍。

IUnitOfWorkManager目的是提供一个简洁的IUnitOfWork管理对象,而IUnitOfWork则提供了整个工作单元需要的所有控制(Begin、SaveChanges、Complete、Dispose)。而具体应该如何保证一个线程共用一个工作单元,如何获取当前的工作单元,则由ICurrentUnitOfWorkProvider进行管控,正如在解析UnitOfWorkManager时,说明了它的Current实际上就是调用ICurrentUnitOfWorkProvider实现对象的Current属性。

ICurrentUnitOfWorkProvider

public interface ICurrentUnitOfWorkProvider
{
IUnitOfWork Current { get; set; }
}

ICurrentUnitOfWorkProvider仅仅只声明了一个Current属性,那么重点让我们来看看Current在实现类(CallContextCurrentUnitOfWorkProvider)中是如何写的吧:

[DoNotWire]
public IUnitOfWork Current
{
get { return GetCurrentUow(Logger); }
set { SetCurrentUow(value, Logger); }
}

上面标注的DoNotWire是为了不让IoC进行属性注入,Current内部分别调用了GetCurrentUow和SetCurrentUow,要取值,先要设值,让我来先看看set吧:

private static void SetCurrentUow(IUnitOfWork value, ILogger logger)
{
if (value == null)
{//如果在set的时候设置为null,便表示要退出当前工作单元
ExitFromCurrentUowScope(logger);
return;
} //获取当前工作单元的key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey != null)
{
IUnitOfWork outer;
if (UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out outer))
{
if (outer == value)
{
logger.Warn("Setting the same UOW to the CallContext, no need to set again!");
return;
}
//到这里也就表示当前存在工作单元,那么再次设置工作单元,不是替换掉当前的工作单元而是将当前工作单元作为本次设置的工作单元的外层工作单元
value.Outer = outer;
}
} unitOfWorkKey = value.Id;
if (!UnitOfWorkDictionary.TryAdd(unitOfWorkKey, value))
{//如果向工作单元中添加工作单元失败,便抛出异常
throw new AbpException("Can not set unit of work! UnitOfWorkDictionary.TryAdd returns false!");
}
//设置当前线程的工作单元key
CallContext.LogicalSetData(ContextKey, unitOfWorkKey);
}
private static void ExitFromCurrentUowScope(ILogger logger)
{
//ContextKey为一个常量字符串
//CallContext可以理解为每个线程的独有key,value集合类,每个线程都会有自己的存储区,
// 也就是在线程A中设置一个key为xx的value,在线程B中通过xx取不到,并可以存入相同键的value,但是不会相互覆盖、影响
//根据ContextKey从线程集合中取出当前工作单元key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey == null)
{//没有取到值,表示当前无工作单元
logger.Warn("There is no current UOW to exit!");
return;
} IUnitOfWork unitOfWork;
//UnitOfWorkDictionary类型为ConcurrentDictionary,线程安全字典,用于存储所有工作单元(单线程上最多只能有一个工作单元,但是多线程可能会有多个)
if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork))
{//根据key没有取到value,从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
} //从工作单元集合中移除当前工作单元
UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork);
if (unitOfWork.Outer == null)
{//如果当前工作单元没有外层工作单元,则从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
} var outerUnitOfWorkKey = unitOfWork.Outer.Id; //这里也就表明了key实际上就是UnitOfWork的Id
if (!UnitOfWorkDictionary.TryGetValue(outerUnitOfWorkKey, out unitOfWork))
{//如果当前工作单元有外层工作单元,但是从工作单元集合中没有取到了外层工作单元,那么同样从线程集合(CallContext)中释放该key
CallContext.FreeNamedDataSlot(ContextKey);
return;
}
//能到这里,就表示当前工作单元有外层工作单元,并且从工作单元集合中获取到了外层工作单元,那么就设外层工作单元为当前工作单元
CallContext.LogicalSetData(ContextKey, outerUnitOfWorkKey);
}

看完set,再让我们来看看get吧:

private static IUnitOfWork GetCurrentUow(ILogger logger)
{
//获取当前工作单元key
var unitOfWorkKey = CallContext.LogicalGetData(ContextKey) as string;
if (unitOfWorkKey == null)
{
return null;
} IUnitOfWork unitOfWork;
if (!UnitOfWorkDictionary.TryGetValue(unitOfWorkKey, out unitOfWork))
{//如果根据key获取不到当前工作单元,那么就从当前线程集合(CallContext)中释放key
CallContext.FreeNamedDataSlot(ContextKey);
return null;
} if (unitOfWork.IsDisposed)
{//如果当前工作单元已经dispose,那么就从工作单元集合中移除,并将key从当前线程集合(CallContext)中释放
logger.Warn("There is a unitOfWorkKey in CallContext but the UOW was disposed!");
UnitOfWorkDictionary.TryRemove(unitOfWorkKey, out unitOfWork);
CallContext.FreeNamedDataSlot(ContextKey);
return null;
} return unitOfWork;
}

总的说来,所有的工作单元存储在线程安全的字典对象中(ConcurrentDictionary),每个主线程共用一个工作单元的实现,通过线程集合(CallContext)实现。

UnitOfWork实现

从上面的分析可以看出,ABP/Domain/Uow路径下,主要只是提供了一套抽象接口,并没有提供实际的实现,IUnitOfWork最多也只是提供了一个UnitOfWorkBase抽象类,这样的自由性非常大,我非常喜欢这种方式。

当然ABP也另起了几个项目来提供一些常用的ORM的Unit of Work封装:

1.Ef:           Abp.EntityFramework/EntityFramework/Uow

2.NH:          Abp.NHibernate/NHibernate/Uow

3.Mongo:     Abp.MongoDB/MongoDb/Uow

4.Memory:   Abp.MemoryDb/MemoryDb/Uow

其中Mongo和Memory都没有进行实质性的单元操作,Ef中使用TransactionScope进行单元操作,NH中使用ITransaction来进行单元操作。

ABP/Domain/Uow结构说明

UnitOfWorkRegistrar····································注册拦截器,实现两种默认的UnitOfWork,详见最上面的默认行为

UnitOfWorkInterceptor··································Unit of Work拦截器,实现以AOP的方式进行注入单元控制

IUnitOfWorkManager····································简洁的UnitOfWork管理对象

 UnitOfWorkManager··································IUnitOfWorkManager默认实现

ICurrentUnitOfWorkProvider···························当前UnitOfWork管理对象

 CallContextCurrentUnitOfWorkProvider············ICurrentUnitOfWorkProvider默认实现

IUnitOfWork···············································工作单元对象(Begin、SaveChanges、Complete、Dispose)

 UnitOfWorkBase·······································IUnitOfWork抽象实现类,封装实际操作的前后置操作及异常处理

 NullUnitOfWork········································IUnitOfWork空实现

IActiveUnitOfWork·······································IUnitOfWork操作对象,不包含Begin与Complete操作

IUnitOfWorkCompleteHandle··························工作单元完成对象,用于实现继承工作单元功能

 InnerUnitOfWorkCompleteHandle··················IUnitOfWorkCompleteHandle实现之一,用于继承外部工作单元

IUnitOfWorkDefaultOptions····························UnitOfWork默认设置

 UnitOfWorkDefaultOptions··························IUnitOfWorkDefaultOptions默认实现

UnitOfWorkOptions·····································UnitOfWork配置对象

UnitOfWorkAttribute····································标记工作单元的特性

UnitOfWorkFailedEventArgs··························UnitOfWork的Failed事件参数

UnitOfWorkHelper·····································工具类

AbpDataFilters·········································数据过滤相关

DataFilterConfiguration·······························数据过滤相关

 
作者:一杯咖啡

Unit of Work的更多相关文章

  1. ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...

  2. ABP源码分析十:Unit Of Work

    ABP以AOP的方式实现UnitOfWork功能.通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类 ...

  3. Failed to stop iptables.service: Unit iptables.service not loaded.

    redhat 7 [root@lk0 ~]# service iptables stop Redirecting to /bin/systemctl stop iptables.service Fai ...

  4. VS2012 Unit Test 个人学习汇总(含目录)

    首先,给出MSDN相关地址:http://msdn.microsoft.com/en-us/library/Microsoft.VisualStudio.TestTools.UnitTesting.a ...

  5. VS2012 Unit Test —— 我对IdleTest库动的大手术以及对Xml相关操作进行测试的方式

    [1]我的IdleTest源码地址:http://idletest.codeplex.com/ [2]IdleTest改动说明:2013年10月份在保持原有功能的情况下对其动了较大的手术,首先将基本的 ...

  6. VS2012 Unit Test——Microsoft Fakes入门

    如题,本文主要作为在VS2012使用Fakes的入门示例,开发工具必须是VS2012或更高版本. 关于Fakes的MSDN地址:http://msdn.microsoft.com/en-us/libr ...

  7. MTU(Maximum transmission unit) 最大传输单元

    最大传输单元(Maximum transmission unit),以太网MTU为1500. 不同网络MTU如下: 如果最大报文数据大小(MSS)超过MTU,则会引起分片操作.   路径MTU: 网路 ...

  8. Simulink Memory vs Unit Delay

    Memoryブロック.Unit Delayブロック共に前回の入力値を出力しますが.動作するタイミングが異なります. ●Memoryブロック シミュレーションの各時刻(ステップ)で動作し.「1ステップ」 ...

  9. GRU(Gated Recurrent Unit) 更新过程推导及简单代码实现

    GRU(Gated Recurrent Unit) 更新过程推导及简单代码实现 RNN GRU matlab codes RNN网络考虑到了具有时间数列的样本数据,但是RNN仍存在着一些问题,比如随着 ...

  10. Unit Testing with NSubstitute

    These are the contents of my training session about unit testing, and also have some introductions a ...

随机推荐

  1. CentOS7 编译安装LNMP

    (文章来自:http://www.cnblogs.com/i-it/p/3841840.html,请各位到这个网址去看原文的) LNMP(Linux-Nginx-Mysql-PHP),本文在CentO ...

  2. mysql字符串替换

    数据库是Mysql的.我想把lesson表中的slide_path_dx字段中的类似 http://www.site.com/y/k/aote-02.rar 替换成E:\web\manhua\y\k\ ...

  3. Android开发中验证码的生成

    近期在做电商金融类的项目,验证码的生成方法不可缺少.先学习了一种.经过測试好用.从别处学习的代码,稍修改了一下可选择是否支持识别大写和小写.直接上代码. import android.app.Acti ...

  4. UE4编码规范

    翻译原文为Unreal 的官方!自己看着总结了一下,不一定每条都能对上.不足之处,请多多不吝赐教! 原文地址:  unreal CodingStandard UE4编码规范 在Epic,有简单几条代码 ...

  5. Sencha Architect 2 的使用

    俗话说的好, 工欲善其事必先利其器, 用Sencha开发的语言, 自己可能不太熟悉, 写出来很麻烦, 于是给大家介绍一个工具. 启动程序第一个界面: 单击第一个Go按钮, 创建一个项目.进入以后, 单 ...

  6. String,StringBuffer和StringBuilder的异同

                                                                    String,StringBuffer和StringBuilder的异同 ...

  7. iOS相机去黑框

    自己定义相机的时候,调用系统的相机,因为相机的分辨率,会出现短小的矩形框,总会出现黑色边框,例如以下图: 假设想实现全屏相机的话,这样做就能够了: CALayer *viewLayer = self. ...

  8. tokumx经营报表

    #见数据库列表  show dbs #切换/创建数据库(当创建一个集合(table)的时候会自己主动创建当前数据库) use admin; #添加用户  db.addUser("zhoulf ...

  9. ORACLE单字符函数的函数

     1.           ASCII(C) 说明:返回C的首字符在ASCII码中相应的十进制 举例: SQL>SELECT ASCII('A') A,ASCII('a') B,ASCII( ...

  10. HDU 1695 GCD 欧拉函数+容斥原理+质因数分解

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=1695 题意:在[a,b]中的x,在[c,d]中的y,求x与y的最大公约数为k的组合有多少.(a=1, a ...