摘要: 为了增加自己的阅读量,今天花了600大洋在淘宝上买了个电纸书:Kindle 4,只是这个东东是M国的东东,在国内出版制度没有得到根本改革之前,书本资源也只能依赖伟大的谷歌了。在网上狂找一通,也算找到了一些有意义的资源点,现记录如下: 免费地址: (推荐)子乌书简:http://book.zi5.me/ (推荐)亿年书海:http://www.inien.com/w/#/Index (推荐)苦瓜书盘:http://kgbook.com/ 书仓:http://www.shucang.com/web/ 七彩英语:http://www.qcenglish.com/积分地址 (推荐)E读吧:http..阅读全文
posted @ 2011-12-02 23:49 永远的阿哲 阅读(28) 评论(0) 编辑
摘要: 有一个故事,说是一个教授带了一群学生做研究,其中有一个学生特认真,于是教授就去问他:你每天早上在做什么?答:在做研究;再问:你每天中午在做什么?答:在做研究;又问:那你每天晚上在做什么?答:在做研究。学生本想教授一定会很高兴,谁知教授却道:你每天都用来做研究,那你拿什么时候来思考呢? 这个故事告诉我们,不停的学习不止的向前冲固然重要,但有时停下来进行思考总结更重要! 下面,我就来总结下我现有的知识体系:上图! 知识掌握度大纲(V3.0)更新历史V3.0 2011-5-24V2.1 2010-9-18 V2.0 2010-9-13V1.0 2010-4阅读全文
posted @ 2010-08-06 09:59 永远的阿哲 阅读(211) 评论(2) 编辑
摘要: 更新历史: 2011/11/7更新:加入《今天空气真的“优良”吗?》 2010/11/6更新:加入《锵锵三人行》 2010/9/19更新 :加入《侯悟宇》 2010/8/20更新 记得小的时候,我就特别有想法,别人认为一致的事,我却有不同的见解,然后就不可必免的发生了争论,以致不欢而散。导致最后被别人一致评论为:性格孤僻,与众不同。当前内心还觉得很受伤:不就有点想法嘛,干嘛这么打击人~~ 直到越长越大了,才发现能独立思考,提出不同意件的人,一般都是不平凡的人。而不平凡的人(臭美先),一般都是孤独的。 当然,独立的思维能力不是天生的,而是不断学习不断思考的结果。现在网上资源如此丰富,虽然...阅读全文
posted @ 2010-07-19 23:09 永远的阿哲 阅读(104) 评论(0) 编辑

       最近做项目,需要把DataTable中的数据强类型化.于是试用了下比较常用的AutoMapper,通过看代码中附带的Demo与网上的教程,也算能够勉强使用了,现将学习笔记记录如下:

 


namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //1.普通转换
            Name name1 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .BeforeMap((name, nameDto) => Console.WriteLine("hello world before"))
                .AfterMap((name, nameDto) => Console.WriteLine("hello world after"));
            NameDto nameDto1 = Mapper.Map<Name, NameDto>(name1);
            Console.WriteLine("1");
            Console.WriteLine(nameDto1.FirstName + nameDto1.LastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//整体设置
            
//2.整体即时转换
            Mapper.Reset();
            Name name2 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ConstructUsing(name => new NameDto() { AllName = name.FirstName + name.LastName });
            NameDto nameDto2 = Mapper.Map<Name, NameDto>(name2);
            Console.WriteLine("2");
            Console.WriteLine(nameDto2.AllName);
            Console.WriteLine();
            //Console.ReadKey();

            
//3.整体通过TypeConverter类型转换
            Mapper.Reset();
            Name name3 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ConvertUsing<NameConverter>();
            NameDto nameDto3 = Mapper.Map<Name, NameDto>(name3);
            Console.WriteLine("3");
            Console.WriteLine(nameDto3.AllName);
            Console.WriteLine();
            //Console.ReadKey();

            
//单属性设置
            
//4.属性条件转换
            Mapper.Reset();
            Name name4 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.FirstName, opt => opt.Condition(name => !name.FirstName.Equals("l", StringComparison.OrdinalIgnoreCase)));
            NameDto nameDto4 = Mapper.Map<Name, NameDto>(name4);
            Console.WriteLine("4");
            Console.WriteLine(string.IsNullOrEmpty(nameDto4.FirstName));
            Console.WriteLine();
            //Console.ReadKey();

            
//5.属性忽略
            Mapper.Reset();
            Name name5 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.FirstName, opt => opt.Ignore());
            NameDto nameDto5 = Mapper.Map<Name, NameDto>(name5);
            Console.WriteLine("5");
            Console.WriteLine(string.IsNullOrEmpty(nameDto5.FirstName));
            Console.WriteLine();
            //Console.ReadKey();

            
//6.属性转换
            Mapper.Reset();
            Name name6 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.AllName, opt => opt.MapFrom(name => name.FirstName + name.LastName));
            NameDto nameDto6 = Mapper.Map<Name, NameDto>(name6);
            Console.WriteLine("6");
            Console.WriteLine(nameDto6.AllName);
            Console.WriteLine();
            //Console.ReadKey();

            
//7.属性通过ValueResolver转换
            Mapper.Reset();
            Name name7 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, StoreDto>()
                .ForMember(storeDto => storeDto.Name, opt => opt.ResolveUsing<NameResolver>());
            StoreDto store1 = Mapper.Map<Name, StoreDto>(name7);
            Console.WriteLine("7");
            Console.WriteLine(store1.Name.FirstName + store1.Name.LastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//8.属性填充固定值
            Mapper.Reset();
            Name name8 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.AllName, opt => opt.UseValue<string>("ljzforever"));
            NameDto nameDto8 = Mapper.Map<Name, NameDto>(name8);
            Console.WriteLine("8");
            Console.WriteLine(nameDto8.AllName);
            Console.WriteLine();
            //Console.ReadKey();

            
//9.属性格式化
            Mapper.Reset();
            Name name9 = new Name() { FirstName = "L", LastName = "jz" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.FirstName, opt => opt.AddFormatter<StringFormatter>());
            NameDto nameDto9 = Mapper.Map<Name, NameDto>(name9);
            Console.WriteLine("9");
            Console.WriteLine(nameDto9.FirstName);
            Console.WriteLine();
            //Console.ReadKey();

            
//10.属性null时的默认值
            Mapper.Reset();
            Name name10 = new Name() { FirstName = "L" };
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.LastName, opt => opt.NullSubstitute("jz"));
            NameDto nameDto10 = Mapper.Map<Name, NameDto>(name10);
            Console.WriteLine("10");
            Console.WriteLine(nameDto10.LastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//其它设置与特性
            
//11.转换匿名对象
            Mapper.Reset();
            object name11 = new { FirstName = "L", LastName = "jz" };
            NameDto nameDto11 = Mapper.DynamicMap<NameDto>(name11);
            Console.WriteLine("11");
            Console.WriteLine(nameDto11.FirstName + nameDto11.LastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//12.转换DataTable
            Mapper.Reset();
            DataTable dt = new DataTable();
            dt.Columns.Add("FirstName"typeof(string));
            dt.Columns.Add("LastName"typeof(string));
            dt.Rows.Add("L""jz");
            List<NameDto> nameDto12 = Mapper.DynamicMap<IDataReader, List<NameDto>>(dt.CreateDataReader());
            Console.WriteLine("12");
            Console.WriteLine(nameDto12[0].FirstName + nameDto12[0].LastName);
            Console.WriteLine();
            //Console.ReadKey();
            
//emitMapper error
            
//List<NameDto> nameDto20 = EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<IDataReader, List<NameDto>>().Map(dt.CreateDataReader());


            
//13.转化存在的对象
            Mapper.Reset();
            Mapper.CreateMap<Name, NameDto>()
                .ForMember(name => name.LastName, opt => opt.Ignore());
            Name name13 = new Name() { FirstName = "L" };
            NameDto nameDto13 = new NameDto() { LastName = "jz" };
            Mapper.Map<Name, NameDto>(name13, nameDto13);
            //nameDto13 = Mapper.Map<Name, NameDto>(name13);//注意,必需使用上面的写法,不然nameDto13对象的LastName属性会被覆盖
            Console.WriteLine("13");
            Console.WriteLine(nameDto13.FirstName + nameDto13.LastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//14.Flatten特性
            Mapper.Reset();
            Mapper.CreateMap<Store, FlattenName>();
            Store store2 = new Store() { Name = new Name() { FirstName = "L", LastName = "jz" } };
            FlattenName nameDto14 = Mapper.Map<Store, FlattenName>(store2);
            Console.WriteLine("14");
            Console.WriteLine(nameDto14.NameFirstname + nameDto14.NameLastName);
            Console.WriteLine();
            //Console.ReadKey();

            
//15.将Dictionary转化为对象,现在还不支持
            Mapper.Reset();
            Mapper.CreateMap<Dictionary<stringobject>, Name>();
            Dictionary<stringobject> dict = new Dictionary<stringobject>();
            dict.Add("FirstName""L");
            //Name name15 = Mapper.DynamicMap<Dictionary<string, object>, Name>(dict);
            Name name15 = Mapper.Map<Dictionary<stringobject>, Name>(dict);
            Console.WriteLine("15");
            Console.WriteLine(name15.FirstName);
            Console.WriteLine();
            Console.ReadKey();
        }
    }

    
    public class Store
    {
        public Name Name { getset; }
        public int Age { getset; }
    }

    public class Name
    {
        public string FirstName { getset; }
        public string LastName { getset; }
    }

    public class StoreDto
    {
        public NameDto Name { getset; }
        public int Age { getset; }
    }

    public class NameDto
    {
        public string FirstName { getset; }
        public string LastName { getset; }
        public string AllName { getset; }
    }

    public class FlattenName
    {
        public string NameFirstname { getset; }
        public string NameLastName { getset; }
    }

    public class NameConverter : TypeConverter<Name, NameDto>
    {
        protected override NameDto ConvertCore(Name source)
        {
            return new NameDto() { AllName = source.FirstName + source.LastName };
        }
    }

    public class NameResolver : ValueResolver<Name, NameDto>
    {
        protected override NameDto ResolveCore(Name source)
        {
            return new NameDto() { FirstName = source.FirstName, LastName = source.LastName };
        }
    }

    public class NameFormatter : ValueFormatter<NameDto>
    {
        protected override string FormatValueCore(NameDto name)
        {
            return name.FirstName + name.LastName;
        }
    }

    public class StringFormatter : ValueFormatter<string>
    {
        protected override string FormatValueCore(string name)
        {
            return name + "-";
        }
    }
}

 

 

      比较遗憾的是现在还不支持将Directory转化为对象

      参考的文章:

  1. 使用AutoMapper实现Dto和Model的自由转换
  2. 6
posted @ 2011-12-29 00:22 永远的阿哲 阅读(30) 评论(0) 编辑

       最近一段时间由于工作需要,仔细研读了微软企业库的部分源码,不由得佩服这些大洋彼岸的同行们.先不谈代码的架构怎么样,起码在代码注释这一块,那叫一个专业啊.一个200行的源文件150行注释50行代码是常有的事.注释量不仅多,质量也高.我的很多困惑都是通过阅读代码注释得以解答的.

      这年头,代码注释的方式基本都是采用以///开头的xml注释方式了.在visual studio里,连续输入三个///,编辑器会自动补全剩下的部分.默认使用的是summary标签.如果是方法则可能还会有param与returns标签.这也是我们最常用到的三个标签.难道xml注释方式只有这三种标签吗?显然不是.当你再输入一个<的时候,编辑器就会自动提示还可以使用的标签.粗粗一算,整整20个,可真不少呀.

      上网查阅了一下,其实常用的只有11个标签左右,下面大概介绍一下

      summary,被注释对象的摘要

      example,展现被注释对象的一个示例

      c,在注释中写代码,当代码只有一行的时候使用.通常与example配合使用

      code,在注释中写代码,当代码有多行时使用.通常与example配合使用

      param,被注释对像的参数,有一个name属性,指定注释哪一个参数

      returns,被注释对像的返回值

      exception,被注释对象执行时可能抛出的异常.有一个cref属性,指定异常类型

      remarks,被注释对象的备注.用来详细描述被注释对象

      para,段落标签. 类似于HTML里的P标签.写在里面的内容会作为单独的一段

      paramref,引用参数.在摘要,备注或其它地方如果要对参数进行说明,如果将参数以该标签包裹,最终生成的chm文档时会生成一个超键接指向该参数,有一个name属性,指定参数名称

      see,参考标签.有一个cref属性,指定参考类型名.

 

      以上11个标签在使用上各不相同.首先,只有被summary包裹或param,returns,para这四个标签才能在visual studio的智能感知里看到效果.你单独写一个remarks标签vs是感知不到的,其它的注释效果需要用工具生成chm后才能看到.其次,summary,example,returns,remarks,para会成普通文字格式,c,code会生成代码格式,exception,see会生成一个超链接链向指定类型.更加详细的说明可参考本文最后的链接.

 

      现在再来谈一谈注释生成工具的使用.最早的工具是微软的御用工具DocGen,开源的NDoc,还有一个通用工具DoxyGen,但是现在这几个要么不好用,生成的文档不符合.net习惯,要么软件停止更新了,不好获取了.现在用的比较多的是Sandcastle.个人体验了一下,效果不错!更加详细的说明可参考本文最后的链接.


      文章的最后还是放上一段个人比较喜爱的一句话,与君共勉:

 

      傻瓜都可以写出机器能读懂的代码,但只有专业程序员才能写出人能读懂的代码

 

      参考的文章:

  1. C# and XML Source Code Documentation

posted @ 2011-12-19 23:54 永远的阿哲 阅读(26) 评论(0) 编辑

       事情的初衷很简单,就是想不用xml配置来使用其缓存组件,试了很多遍都无法成功.不得已安装了其源码大略分析一遍,才总算成功.后来又一想,既然分析就分析的彻底一点吧,顺便看看国外的高手们是怎么架构组件,书写代码的,于是就有了这篇文章.企业库为5.0版本.

      首先是类关系图:

 

 

      缓存组件的整体结构为CacheManager -> Cache -> CacheItem,其中CacheItem为缓存项,其有Key有Value,还有本缓存项的过期策略及删除时的回调函数.Cache为缓存,除管理CacheItem外,还负责管理缓存性能计算器及缓存持久化.CacheManager为Cache类的包装类,用户调用接口,也是最为我们熟悉的,其代理了Cache类的缓存操作方法,此外还有过期轮询等.下面就来一步一步的分析.

      一.缓存创建


 

       常见的缓存创建方式为:

ICacheManager manager = Microsoft.Practices.EnterpriseLibrary.Caching.CacheFactory.GetCacheManager();

      其实还有一种创建方式:

CacheManagerFactory factory = new CacheManagerFactory();
ICacheManager manager = factory.CreateDefault();

      这两种方式创建缓存,本质上调用的都是这段代码:

EnterpriseLibraryContainer.Current.GetInstance<ICacheManager>(cacheManagerName)

      EnterpriseLibraryContainer对象,又称企业库容器对象,说白了就是个依赖注入的容器,封装了unity框架.更具体的说明,请参见我另写的一篇文章:代码级浅析企业库对象创建

      这段代码的意思,就是返回一个注册了的实现了ICacheManager接口的类.这里实际返回的是CacheManager类.

 

      二.缓存轮询

 

      缓存配置中有两个参数用在了这里:numberToRemoveWhenScavenging和maximumElementsInCacheBeforeScavenging.参数的名字已经把他们的用途说的很明白了:缓存里存储了多少项数据后启动清理及每次移除多少项数据.

 

1 public void Add(string key, object value, CacheItemPriority scavengingPriority, ICacheItemRefreshAction refreshAction, params ICacheItemExpiration[] expirations)
2 {
3     realCache.Add(key, value, scavengingPriority, refreshAction, expirations);
4     
5     backgroundScheduler.StartScavengingIfNeeded();
6 }

     

      代码第六行说的很清楚,每次新增缓存项时,就会检查缓存项是否超过了配置值.如果超过了,就会通过多线程的方式在线程池中执行以下方法

 

internal void Scavenge()
{
    int pendingScavengings = Interlocked.Exchange(ref scavengePending, 0);
    int timesToScavenge = ((pendingScavengings - 1) / scavengerTask.NumberOfItemsToBeScavenged) + 1;
    while (timesToScavenge > 0)
    {
        scavengerTask.DoScavenging();
        --timesToScavenge;
    }
}

      然后又调用了ScavengerTask类的DoScavenging方法

 1 public void DoScavenging()
 2 {
 3     if (NumberOfItemsToBeScavenged == 0return;
 4 
 5     if (IsScavengingNeeded())
 6     {
 7         Hashtable liveCacheRepresentation = cacheOperations.CurrentCacheState;
 8 
 9         ResetScavengingFlagInCacheItems(liveCacheRepresentation);
10         SortedList scavengableItems = SortItemsForScavenging(liveCacheRepresentation);
11         RemoveScavengableItems(scavengableItems);
12     }
13 }


      这是实际实现功能的方法.如果缓存项多于配置值时就会执行.第9行代码将缓存项的eligibleForScavenging字段设为true,表示可以对其做扫描移除工作.其实与这个字段相对应的EligibleForScavenging属性并不是简单的返回这个字段,其还考虑了缓存项的优先级,只有eligibleForScavenging为true且优先级不为最高(NotRemovable),才返回true.第10行即对缓存项做排序工作,以优先级为排序字段将缓存排序,优先级最高的排在后面,表示最后才被删除.第11行则是真正删除方法.在其方法体内会遍例排序之后的缓存项,如果EligibleForScavenging属性为true则删除,还有个变量记录了删除的个数.如果其等于配置值,则停止删除.

      可以看到缓存轮询与缓存过期无关,缓存优先级与缓存过期也没关系.那么经过扫描后的缓存,仍然可能存在已过期项.

 

      三.缓存过期

      

      在配置文件中有一个配置与此有关:expirationPollFrequencyInSeconds,则每隔多长时间对缓存项进行一次过期检查.

pollTimer.StartPolling(backgroundScheduler.ExpirationTimeoutExpired);

      在缓存容器CacheManger创建时就会开始计时

pollTimer = new Timer(callbackMethod, null, expirationPollFrequencyInMilliSeconds, expirationPollFrequencyInMilliSeconds);

      其本质是一个Timer对象,定时回调指定的函数.这里的回调函数其实是BackgroundScheduler对象的Expire方法:

 

internal void Expire()
{
    expirationTask.DoExpirations();
}

      其又调用了ExpirationTask对象的DoExpirations方法:

 

1 public void DoExpirations()
2 {
3     Hashtable liveCacheRepresentation = cacheOperations.CurrentCacheState;
4     MarkAsExpired(liveCacheRepresentation);
5     PrepareForSweep();
6     int expiredItemsCount = SweepExpiredItemsFromCache(liveCacheRepresentation);
7     
8     if(expiredItemsCount > 0) instrumentationProvider.FireCacheExpired(expiredItemsCount);
9 }

      这里是过期的实际功能方法.代码第四行遍例缓存,将已过期的缓存的WillBeExpired属性标记为true,第6行则是将所有WillBeExpired属性标记为true的缓存项进行删除.下面来看如何判断一个缓存项是否过期.

      其实新增缓存的方法有多个重载,其中一个就是

public void Add(string key, object value, CacheItemPriority scavengingPriority, ICacheItemRefreshAction refreshAction, params ICacheItemExpiration[] expirations)


 

      常见的有绝对时间,相对时间,文件依赖等.可以看到,一个缓存项,是可以有多个缓存依赖的,或者叫缓存过期策略.如果其中任意一个过期,则缓存项过期.

public bool HasExpired()
{
    foreach (ICacheItemExpiration expiration in expirations)
    {
        if (expiration.HasExpired())
        {
            return true;
        }
    }

    return false;
}

      对缓存项过期的管理,除定时轮询外,在取值的时候,也会判断.

 

      四.缓存回调

 

      如果缓存项从缓存中移除,则会触发回调:

RefreshActionInvoker.InvokeRefreshAction(cacheItemBeforeLock, removalReason, instrumentationProvider);

      实际上是以多线程的方式在线程池中执行回调函数

 

public void InvokeOnThreadPoolThread()
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolRefreshActionInvoker));
}

private void ThreadPoolRefreshActionInvoker(object notUsed)
{
    try
    {
        RefreshAction.Refresh(KeyToRefresh, RemovedData, RemovalReason);
    }
    catch (Exception e)
    {
        InstrumentationProvider.FireCacheCallbackFailed(KeyToRefresh, e);
    }
}

 

      五.线程安全

      企业库用了大量的代码来实现了缓存增,删,取值的线程安全.它用了两个锁来实现线程安全.新增操作最复杂,就分析它吧

 1 public void Add(string key, object value, CacheItemPriority scavengingPriority, ICacheItemRefreshAction refreshAction, params ICacheItemExpiration[] expirations)
 2 {
 3     ValidateKey(key);
 4 
 5     CacheItem cacheItemBeforeLock = null;
 6     bool lockWasSuccessful = false;
 7 
 8     do
 9     {
10         lock (inMemoryCache.SyncRoot)
11         {
12             if (inMemoryCache.Contains(key) == false)
13             {
14                 cacheItemBeforeLock = new CacheItem(key, addInProgressFlag, CacheItemPriority.NotRemovable, null);
15                 inMemoryCache[key] = cacheItemBeforeLock;
16             }
17             else
18             {
19                 cacheItemBeforeLock = (CacheItem)inMemoryCache[key];
20             }
21 
22             lockWasSuccessful = Monitor.TryEnter(cacheItemBeforeLock);
23         }
24 
25         if (lockWasSuccessful == false)
26         {
27             Thread.Sleep(0);
28         }
29     } while (lockWasSuccessful == false);
30 
31     try
32     {
33         cacheItemBeforeLock.TouchedByUserAction(true);
34 
35         CacheItem newCacheItem = new CacheItem(key, value, scavengingPriority, refreshAction, expirations);
36         try
37         {
38             backingStore.Add(newCacheItem);
39             cacheItemBeforeLock.Replace(value, refreshAction, scavengingPriority, expirations);
40             inMemoryCache[key] = cacheItemBeforeLock;
41         }
42         catch
43         {
44             backingStore.Remove(key);
45             inMemoryCache.Remove(key);
46             throw;
47         }
48         instrumentationProvider.FireCacheUpdated(1, inMemoryCache.Count);
49     }
50     finally
51     {
52         Monitor.Exit(cacheItemBeforeLock);
53     }  
54 
55 }

      代码第10行首先锁住整个缓存,然后新增一个缓存项并把他加入缓存,然后在第22行尝试锁住缓存项并释放缓存锁.如果没有成功锁上缓存项,则重复以上动作.在代码14与15行可以看到,这时加入缓存的缓存项并没有存储实际的值.他相当于一个占位符,表示这个位置即将有值.如果成功锁住了缓存项,代码第39号则是以覆盖的方式将真正的值写入缓存.

      这里为什么要用两个锁呢?我觉得这是考虑到性能.用一个锁锁住整个缓存完成整个操作固然没有问题,但是如果代码第33行或第38号耗时过多的话,会影响整个系统的性能,特别是第38行,涉及IO操作,更是要避免!那为什么在第14行使用的是占位符而不是真正的存储呢?我觉得这也是考虑到性能.这里的新增操作包括两个含义,缓存中不存在则新增,存在则更新.这里考虑的是更新的问题.通常做法是让缓存项指向新对象,这样先前指向的对象就会成为垃圾对象.在高负载的应用程序里,这会产生大量的垃圾对象,影响了系统的性能.如果通过Replace的方式来操作,则可以必免这个问题,让缓存项始终指向一个内存地址,只是更新他的内容而以.

      六.离线存储(缓存持久化)

 

 

      通过这个功能,可以让内存数据保存在硬盘上.在缓存初始化的时候会从硬盘上加载数据

Hashtable initialItems = backingStore.Load();
inMemoryCache = Hashtable.Synchronized(initialItems);

      在新增与删除的时候,会在硬盘上做相应的操作

backingStore.Add(newCacheItem);

 

backingStore.Remove(key);

      在企业库里,是通过.net的IsolatedStorageFile类来实现其功能的.每个缓存都对应一个目录

 

private void Initialize()
{
    store = IsolatedStorageFile.GetUserStoreForDomain();
    if (store.GetDirectoryNames(storageAreaName).Length == 0)
    {
        // avoid creating if already exists - work around for partial trust
        store.CreateDirectory(storageAreaName);
    }
}

      每个缓存项则是一个子目录,缓存项里的每个对象则被序列化成单个文件

 

return Path.Combine(storageAreaName, itemToLocate);

 

 1 public IsolatedStorageCacheItem(IsolatedStorageFile storage, string itemDirectoryRoot, IStorageEncryptionProvider encryptionProvider)
 2 {
 3     if (storage == nullthrow new ArgumentNullException("storage");
 4 
 5     int retriesLeft = MaxRetries;
 6     while (true)
 7     {
 8         // work around - attempt to write a file in the folder to determine whether delayed io
 9         // needs to be processed
10         // since only a limited number of retries will be attempted, some extreme cases may 
11         // still fail if file io is deferred long enough.
12         // while it's still possible that the deferred IO is still pending when the item that failed
13         // to be added is removed by the cleanup code, thus making the cleanup fail, 
14         // the item should eventually be removed (by the original removal)
15         try
16         {
17             storage.CreateDirectory(itemDirectoryRoot);
18 
19             // try to write a file
20             // if there is a pending operation or the folder is gone, this should find the problem
21             // before writing an actual field is attempted
22             using (IsolatedStorageFileStream fileStream =
23                 new IsolatedStorageFileStream(itemDirectoryRoot + @"\sanity-check.txt", FileMode.Create, FileAccess.Write, FileShare.None, storage))
24             { }
25             break;
26         }
27         catch (UnauthorizedAccessException)
28         {
29             // there are probably pending operations on the directory - retry if allowed
30             if (retriesLeft-- > 0)
31             {
32                 Thread.Sleep(RetryDelayInMilliseconds);
33                 continue;
34             }
35 
36             throw;
37         }
38         catch (DirectoryNotFoundException)
39         {
40             // a pending deletion on the directory was processed before creating the file
41             // but after attempting to create it - retry if allowed
42             if (retriesLeft-- > 0)
43             {
44                 Thread.Sleep(RetryDelayInMilliseconds);
45                 continue;
46             }
47 
48             throw;
49         }
50     }
51 
52     keyField = new IsolatedStorageCacheItemField(storage, "Key", itemDirectoryRoot, encryptionProvider);
53     valueField = new IsolatedStorageCacheItemField(storage, "Val", itemDirectoryRoot, encryptionProvider);
54     scavengingPriorityField = new IsolatedStorageCacheItemField(storage, "ScPr", itemDirectoryRoot, encryptionProvider);
55     refreshActionField = new IsolatedStorageCacheItemField(storage, "RA", itemDirectoryRoot, encryptionProvider);
56     expirationsField = new IsolatedStorageCacheItemField(storage, "Exp", itemDirectoryRoot, encryptionProvider);
57     lastAccessedField = new IsolatedStorageCacheItemField(storage, "LA", itemDirectoryRoot, encryptionProvider);
58 }

 

      至于IsolatedStorageFile这个类.我查了一下,这个类在sl或wp中用的比较多.这个类更具体的信息,各位看官自行谷歌吧.

 

      七.性能记数器

 

      这个就没什么说的了,就是将各种缓存的操作次数记录下来,包括成功的次数与失败的次数.CachingInstrumentationProvider类里包含了13个EnterpriseLibraryPerformanceCounter类型的计数器.这种计数器其实是系统计数器PerformanceCounter类型的封装.这13个计数器分别为:命中/秒,总命中数,未命中/秒,总未命中数,命中比,缓存总记问数,过期数/秒,总过期数,轮询清除/秒,总轮询清除数,缓存项总数,更新缓存项/秒,更新缓存项总数.更加具体的信息,各位看官自行谷歌吧.

 

      至此,缓存组件的分析告一段落了.我感觉缓存组件比上一篇写到的对象创建模块要好的很多,代码结构清晰,职责分明.里面涉及的众多技术运用,如多线程,锁,性能,面向接口编程等也较为合理,算的上是一个学习的样本.

 

      文章的最后放上一段我最喜欢的一句话吧:

 

      “设计软件有两种策略,一是做的非常的简单,以至于明显没有缺陷。二是做的非常的复杂,以至于没有明显的缺陷。” – C.A.R. Hoare
 

 

posted @ 2011-12-19 00:50 永远的阿哲 阅读(1349) 评论(3) 编辑

       本来是没有打算写这篇分析的,但是在我分析缓存组件的时候,发现企业库的所有组件都是通过一种方式创建出来的,这就让我产生了好奇,于是决定去看看他到底是如何通过配置文件将正确的对象创建出来.

 

 

      这里有四个重要的接口,一句话概括,就是配置器(IContainerConfigurator)以特定的解析方式(ITypeRegistrationsProvider)将源(IConfigurationSource)里的信息解析出来,最终发布为服务定位器(IServiceLocator).

 

      一.服务定位器

 

      可以从图上看到,其实所谓的企业库服务定位器,其实就是对依赖注入框架Unity的一个封装,通过GetInstance<T>方法来解析接口及其实现类的匹配.

 

      二.配置器

 

      配置器是一个完成类型注册的过程.从图上可以看到,Unity容器内的注册信息是通过UnityContainerConfigurator来进行注册的.进行注册的代码为:

 

container.RegisterType(registrationEntry.ServiceType, registrationEntry.ImplementationType, registrationEntry.Name, CreateLifetimeManager(registrationEntry), GetInjectionMembers(registrationEntry));

 

      其本质是向Unity容器进行类型注册.注册完成之后,就可以通过UnityServiceLocator向外提供服务了.

 

      三.配置源


      我们的配置信息一般写在系统的配置文件中,而IconfigurationSource接口则负责从文件中读取相关信息.从上图可以看到,真正的实现类是SystemConfigurationSource类. 在其里面有这样一句代码:

return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

      这也决定了系统只能从默认的配置文件中读取信息.

 

      四.解析方式

 

      可以从图中看到,所以解析,就是把配置文件分析成一个接口与实现类的映射表.TypeRegistrationProvidersConfigurationSection,TypeRegistrationProviderElementCollection与TypeRegistrationProviderElement,分别对应配置文件里的配置节,配置元素集合与配置元素,BlockSectionNames类则定义了企业库使用的配置节名称.重点看ConfigurationBasedTypeRegistrationsProviderFactory类的CreateTypeRegistrationsProviderLocators方法,代码如下:

 

 1 TypeRegistrationProvidersConfigurationSection section = configurationSource.GetSection(TypeRegistrationProvidersConfigurationSection.SectionName) as TypeRegistrationProvidersConfigurationSection;
 2 if (section == null)
 3 {
 4     section = new TypeRegistrationProvidersConfigurationSection();
 5 }
 6 
 7 foreach (TypeRegistrationProviderElement typeRegistrationProviderElement in section.TypeRegistrationProviders)
 8 {
 9     if (!string.IsNullOrEmpty(typeRegistrationProviderElement.SectionName) &&
10         !string.IsNullOrEmpty(typeRegistrationProviderElement.ProviderTypeName))
11     {
12         throw new ConfigurationErrorsException(
13             string.Format("Type Registration Provider Settings '{0}' cannot declare both sectionName and providerType attributes",
14             typeRegistrationProviderElement.Name));
15     }
16     if (!string.IsNullOrEmpty(typeRegistrationProviderElement.SectionName))
17     {
18         yield return new ConfigSectionLocator(typeRegistrationProviderElement.SectionName, reconfiguringEventSource);
19     }
20     else if (!string.IsNullOrEmpty(typeRegistrationProviderElement.ProviderTypeName))
21     {
22         yield return new TypeLoadingLocator(typeRegistrationProviderElement.ProviderTypeName, reconfiguringEventSource);
23     }
24 }

      第四行新建了一个TypeRegistrationProvidersConfigurationSection对象,其TypeRegistrationProviders属性在返回时,会创建库业库默认使用的节点集合,代码如下:

public TypeRegistrationProviderElementCollection()
{
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.CachingTypeRegistrationProviderName, SectionName = BlockSectionNames.Caching });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.CryptographyTypeRegistrationProviderName, SectionName = BlockSectionNames.Cryptography });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.ExceptionHandlingTypeRegistrationProviderName, SectionName = BlockSectionNames.ExceptionHandling });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.InstrumentationTypeRegistrationProviderName, SectionName = BlockSectionNames.Instrumentation });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.LoggingTypeRegistrationProviderName, SectionName = BlockSectionNames.Logging });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.PolicyInjectionTypeRegistrationProviderName, SectionName = BlockSectionNames.PolicyInjection });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.SecurityTypeRegistrationProviderName, SectionName = BlockSectionNames.Security });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.DataAccessTypeRegistrationProviderName, ProviderTypeName = BlockSectionNames.DataRegistrationProviderLocatorType });
    BaseAdd(new TypeRegistrationProviderElement() { Name = TypeRegistrationProvidersConfigurationSection.ValidationTypeRegistrationProviderName,  ProviderTypeName = BlockSectionNames.ValidationRegistrationProviderLocatorType });
}

 

      下面就是读取这些节点的信息了, 代码第18行与第22行就是跟据不同的情况创建不同的定位器.

      到目前为止, 只是知道了企业库会使用哪些配置节,那具体的分析工作呢,还是回到UnityContainerConfigurator类吧:

 1 protected override void RegisterAllCore(IConfigurationSource configurationSource, ITypeRegistrationsProvider rootProvider)
 2 {
 3     EnterWriteLock();
 4     try
 5     {
 6         foreach (var registration in rootProvider.GetRegistrations(configurationSource))
 7         {
 8             Register(registration);
 9         }
10     }
11     finally
12     {
13         ExitWriteLock();
14     }
15 }

      可以看到由ITypeRegistrationsProvider接口完成.而TypeRegistrationsProvider抽象类实现了这个接口,TypeLoadingLocator与ConfigSectionLocator又继承了这个抽象类.随便看一个吧:

public override IEnumerable<TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
{
    return GetRegistrationsInternal(configurationSource, (p, cs) => p.GetRegistrations(cs));
}

 

 1 private IEnumerable<TypeRegistration> GetRegistrationsInternal(IConfigurationSource configurationSource,
 2     Func<ITypeRegistrationsProvider, IConfigurationSource, IEnumerable<TypeRegistration>> registrationsAccessor)
 3 {
 4     ITypeRegistrationsProvider provider = null;
 5     ConfigurationSection section = configurationSource.GetSection(Name);
 6     if (section != null)
 7     {
 8         provider = section as ITypeRegistrationsProvider;
 9     }
10 
11     if (provider != null)
12     {
13         return registrationsAccessor(provider, configurationSource);
14     }
15     return Enumerable.Empty<TypeRegistration>();
16 }

      其首先从源中获取指定节点,然后把其强转成ITypeRegistrationsProvider再调用其GetRegistrations方法获取真正的配置信息.比如缓存配置,用GetSection获取后其实就是CacheManagerSettings类,其实现了ITypeRegistrationsProvider接口,调用其GetRegistrations方法解析缓存配置.


      是不是感觉很乱?反正我是这么认为的.搞不懂不就是解析一个配置文件嘛,干嘛要搞这么复杂,难道不多转几个弯就不能体现企业库的NB与价值?!个人感觉最乱的就是对于ITypeRegistrationsProvider接口的使用.UnityContainerConfigurator类调用ITypeRegistrationsProvider接口,实际是调用CompositeTypeRegistrationsProviderLocator类.这个类的内部维护了一个ITypeRegistrationsProvider接口集合,其GetRegistrations方法就是遍例这个集合逐个调用各自GetRegistrations方法.实际上这个集合装着的是TypeLoadingLocator类与ConfigSectionLocator类,那么调用的就是这两个类的方法.然后这两个类又把源获取一遍,把获取的结果转成ITypeRegistrationsProvider接口,然后再调转换后的GetRegistrations方法.到了这一步,才真正把该拿的信息拿出来!有必要搞的这么复杂吗?!

 

      参考的文章:

      微软企业库5.0学习笔记

 

posted @ 2011-12-18 18:31 永远的阿哲 阅读(80) 评论(0) 编辑

       为了增加自己的阅读量,今天花了600大洋在淘宝上买了个电纸书:Kindle 4,只是这个东东是M国的东东,在国内出版制度没有得到根本改革之前,书本资源也只能依赖伟大的谷歌了。在网上狂找一通,也算找到了一些有意义的资源点,现记录如下:

      

免费地址:
    (推荐)子乌书简:http://book.zi5.me/
    (推荐)亿年书海:http://www.inien.com/w/#/Index
    (推荐)苦瓜书盘:http://kgbook.com/
    书仓:http://www.shucang.com/web/
    七彩英语:http://www.qcenglish.com/
积分地址
    (推荐)E读吧:http://www.edooba.com/
二级免费地址:
    E书吧:http://www.eshuba.com/
    E书联盟:http://www.book118.com/
收费地址:
    新华E店:http://www.edowenku.com/anonymous/index.xhtml
    番薯网:http://www.fanshu.com/
其它地址:
    (推荐)多看论坛:http://www.duokan.com/forum/index.php
    (推荐)电纸书论坛:http://bbs.mydoo.cn/
    一个关注kindle的博客:http://irising.me/tag/kindle/
    一个书的集合:https://www.dropbox.com/s/b44t0swhzsdlaof
    (推荐)一个书的集合:http://bbs.weiphone.com/read-htm-tid-1511316.html

 

posted @ 2011-12-02 23:49 永远的阿哲 阅读(28) 评论(0) 编辑

     记得大约在一年前,我曾写过相关议题的文章,叫做业务逻辑架构模式(事务脚本,表模块,活动记录,领域模型)再谈业务逻辑架构模式(事务脚本,表模块,活动记录,领域模型),经过这一年多做项目的沉淀,特别是最近十天来,我又仔细研读了<Microsoft.Net企业级应用架构计设>这本书,并上了iteye论坛相阅了相关贴子,发现以前的很多认识是不全面的,甚至是错误的,相关的概念与认识也有了进一步的明确与清淅.觉得甚有必要将最新的一些想法与反思记录下来,达到总结提高的目的.

 

      一.总论

      首先来看一张图:

 

 


      我的第一篇文章里曾出现过类似的图,但是现在若干细节上有所不同,下面一一来分析

 

      二.事务脚本

      这是一种最直接的业务架构模式,他将后台所有的模块(包括我们常说的Dao,Server,Entity,BL层等)整合在了一起形成一个层,使用的是最彻底的用例驱动----UI上有什么样的操作, 这一层就有什么方法.没有专门表达数据结构的类,所有对数据的操作都是直接的Sql方式.

 

      三.表模块

      虽然这种模式还是把所有的模块都整合成一个层,但是已经开始有了对数据基本的抽象了.在.net里,就是将数据库抽象成一个DataSet,将其中的每一张表抽象成一个DataTable.对数据的操作,就是对这些DataSet与DataTable的操作.一般来讲,使用这种模式的BL层的类内置一个DataSet或DataTable类型的成员或属性来装载数据,所以这种BL类一般表达的是对整张表或者是对数据集合的操作,但是因为DataSet或DataTable在表达数据方面是弱类型的,所以在表模块里是没有办法使用强类型的对象的.

 

      四.活动记录

      虽然这种模式还是把所有的模块都整合成一个层,但是与表模块相比,有两个值得注意的地方.

      1.在这种模式里,对于数据的抽象就更进一步了----通过对象来表达数据,也就是对数据的强类型抽象.

      2.一般来讲,使用这种模式的BL层的类,其属性就是数据库的列,一个对象就是数据库中的一行数据,所以其表达的操作是对数据行的操作.这也是与表模块模式最大的不同.

      在.net里,ActiveRecord框架就专门表达这种模式的框架,通过其底层封装的类库与代码生成工具, 能够很快生成与这种模式相适应的.net代码.

 

      五.领域模型

      这种模型是我这篇文章的重点.在领域模型里,通过对BL层的分解来达到复用与灵活.按照iteye资深会员robbin的观点(我也基本赞同),特将此模型按实现方式的不同再分为以下4小类:

      1.失血模型

      在这种模型里,有一个Entity类来抽象数据,其只有属性,没有方法来表达业务,还有一个EntityManage类来表达领域逻辑与业务逻辑.

      2.贫血模型

      在这种模型里,有一个Entity类来抽象数据,用属性表达数据,还有少许不依赖其它层次与技术的领域逻辑,用属性或方法表达.还有一个EntityManage类来表达依赖其它层次与技术的领域逻辑和业务逻辑.

      3.充血模型

      在这种模型里,有一个Entity类来抽象数据,用属性表达数据,还有大部份的领域逻辑与业务逻辑,用属性或方法表达.还有一个EntityManage类,仅仅封装事务和少量逻辑,主要起一个外观的作用.

      4.胀血模型

      这种模型与活动记录比较像,就是不再分层,而是把所有的层全都放在一起.

 

      在这四种模型当中,失血与胀血是应该不被提倡的.胀血模型与活动记录差不多,而把其中所有的逻辑拿到另外一个类中后就成了失血模型,两个都走了极端.剩下的两个模型:贫血模型与充血模型,就要视情况而定了.在我看来,我更推崇贫血的领域.如下图所示:

 

 

 

      先来解释两个名词:领域逻辑与业务逻辑.所谓领域逻辑,就是领域本身特有的,不依赖具体执行环境的逻辑.比如一份合同,有签订时间与有效期,那么计算过期时间就是他的领域逻辑.业务逻辑,则是与领域相关,依赖特定执行环境的逻辑.比如合同订金,是依赖于合同的种类与金额大小的,有的15%,有的3%,有的签订合同当场就要缴纳,有的则有其它的条件与时限.

      首先看领域层.领域层分为两部份:领域对象与领域逻辑.领域对象就是领域层的静态部份,通常表现为属性,在大部份的程序里他的大部份都会与数据库的列一一对应.领域逻辑是领域层的动态部份,是那些不依赖其它层次与技术的领域逻辑.在上图中可以看到我把领域层与表现层衔接了起来,表示领域层的对象也可作为DTO传到表现层上面去.

      然后再看服务层.包括了依赖其它层次与技术的领域逻辑和业务逻辑.服务层是系统的核心,系统中的各个层次与各种技术都要与他发生交互.在上图中,上承表现层,下调领域层,通过IOC注入执行环境,通过AOP分离系统逻辑,通过ORM透明持久化,通过MSDTC定义事务边界等等.

 

      在这里,有几点需要重点说明

      A 在我看来,领域层是可以复用的,是面向对象的,为了达到这个目的,就一定要保证领域层的"纯洁" .所谓"纯洁",就是指:a) 领域层不能依赖任务具体的执行环境.比如我现在在做国土业务,政府在卖地的时候,会发几样凭证,其中一个叫<建设用地批准书>,一个叫<供地合同>.全国各地都是这么发的,但是发证的顺序与科室却有所不同,有的是M部门发A,N部门发B,有的是Q部门AB一起发.为了使这两个领域模型重用,就必需不能包含以上发证逻辑;b) 领域层不能依赖于具体技术实现.比如现在用的比较多的是NH,但是我建议还是不要让领域层依赖于他.万一重用后的环境用的是EF,就麻烦了.有时,为了简化程序层次,也可作为DTO传到表现层上面去.

      B 在我看来,服务层是用例驱动的,是面向过程的,大部份情况下是无法实现复用的.为了帮助领域层保持"纯洁",服务层就不得不"不纯洁"了.就如同上面所说的,上承表现层,下调领域层,通过IOC注入执行环境,通过AOP分离系统逻辑,通过ORM透明持久化,通过MSDTC定义事务边界等等.系统中所有的一切,都在服务层形成了交汇.

      C 在Asp.Net WebForm环境中,有时简单起见,会把页面的CodeBehined代码与Server层合并.这样可以减少系统的层次.不过如果系统比较大,比较复杂,还是不要这么干.

      D 真正好的领域模型是重构出来的.一个行业的信息化是一个从不稳定到稳定的过程,一个业务的理解也是由浅入深的过程.在一开始,可能我们不确定这是不是个核心业务,业务的具体表现形式也可能多变,这时可能我们会把逻辑写到CodeBehined里;经过一段时间的沉淀后,可能觉得这个业务较为通用,表现也趋于稳定,这时我们就可能会把其重构到Server层里;最后我们发现其实这个业务经过更高级别的抽象后有其不变的地方,这时我们就会考虑将其技术细节剥离后放入领域模型.

      E 在我看来,复杂查询,报表与统计在本质上不是面向对象的,而是面向数据的,且业务非常不稳定,那么他们也就不必通过领域模型来完成.而是直接从Server层连接到DB来完成相关功能.

      F 领域驱动设计与领域模型虽然经常配合起来使用,但其实是两个层面上的东西.领域驱动设计,强调的是在业务分析过程中,以业务为核心进行分析建模,是一种分析方法论;而领域模型则是一种强调在技术实现中,以业务为核心,是一种技术实践的方法论.

 

      在我看来,充血模型会提高领域层的耦合性,在业务上耦合了特定业务场景,在技术上耦合了特定技术选型,所以我更倾向于使用贫血模型.

      其实以上文章只是我在结合了自己的理解后炒的冷饭,早在5,6年前,iteye论坛就已经组织讨论过相关话题,02年POEAA就出版了,想到自己的相关技术水平还只停留在别人10年前的水平,感觉很是惭愧.

      以上的贫血模型,其实只能比较好的解决中小规模的项目.一旦项目复杂度上到一定的程度后,此模型仍然运作的不是很理想.下一步,就是要进一步的研究近些年出现的相关知识,如DCI,四色模型,CQRS等.争取从更高层次完成对系统的拆分,实现系统更理想的复用!在这方面,园子里已经有两位先行者了:dax.net与netfocus.有时候,能站在先驱与巨人的肩膀上学习,真是件很幸福的事啊~~~

 

      引用的文章

      一.关于领域模型

      谈一谈贫血的Domain Logic问题

      总结一下最近关于domain object以及相关的讨论

      看看Rod Johnson怎么讲Domain Object

      再次小结领域模型的种种观点

      二.贫血VS充血

      从贫血领域模型到丰富领域模型

      为什么java里不能把域对象和DAO合并,rails里面就可以?

      domain model的延伸讨论

      对Robbin《domain model的延伸讨论(重新编辑) 》一文质疑

      Rich Domain Model In Java ORM

      关于领域模型、DAO的疑问??

      Domain Object贫血vs富血(DDD)和spring roo到ruby的扯淡

      三.其它

      Domain injection with AOP

      命令和查询责任分离(CQRS)架构模式 读后疑惑

      领域模型的价值与困境

      再论领域模型的困境

      运用四色建模法进行领域分析

posted @ 2011-11-20 01:14 永远的阿哲 阅读(995) 评论(3) 编辑
摘要: 最近公司跟我换了新电脑:2代i3+8G内存,由于32位的win7最多只能用3.25G内存,而用ramdisk4g划出4.75G来作硬盘又觉太浪费,遂想用64位的系统. 从网上下载了冷风的64位win7后先装系统再装驱动再装常软件,除部份驱动需重新安装外,一路安好.但是在安装ODAC组件时,出了如下问题: 1.常用的plsql与sqldbx只有32位版本,只能使用32位的ODAC, 2.在64位系统上,如果使用32位的ODAC,asp.net程序时会报异常:尝试加载Oracle客户端库时引发BadImageFomatException。如果在安装32位Oracle客户端组件的情况下以64位...阅读全文
posted @ 2011-10-20 21:03 永远的阿哲 阅读(196) 评论(0) 编辑
摘要: 最近公司项目马上就要在客户这边进入试运行了,而我也被派过来进行项目的初步布署,下面就是我的布署概要图. 通过花生壳,实现将内网主机发布到外网上,这样源代码获取,远程桌面等就方便了很多;通过CCProxy实得原来在内网的服务器可以连接外网,方便项目布署. 在进行项目布署的过程中,我遇到了并解决了以下问题,现在简要记录一下 1).默认网关 在双网卡的电脑上,当你在IE地址栏输入一个网址的时候,浏览器到底是通过网卡一进行通信还是网卡二进行通信的呢?这主要看你当时的默认网关是哪个.对于两个网卡的电脑,网卡的启动是有先后顺序的,一般来讲,先启动的网卡具有优先级,也就会是当前默认网关.通过在cmd中输入.阅读全文
posted @ 2011-08-14 23:27 永远的阿哲 阅读(133) 评论(0) 编辑
摘要: 公司最近准备分出一套人马去客户那里做驻场研发,这就涉及到代码库的统一管理。鉴于VSS在互联网上表现不好,而TFS配置又过于复杂,我们选择了SVN。考虑到代码的安全性及性能,我们决定在本地布署自己的SVN服务器,然后每个人安装TortoiseSVN作为客户端,并安装AnkhSVN插件配合Visual Studio使用,安装CruiseControl.NET进行自动化的每日构建,安装Redmine进行项目管理,安装BugFree进行bug管理。 1.VisualSVN,TortoiseSVN,AnkhSVN 我安装的版本是:VisualSVN2.1.9,TortoiseSVN-1.6.16...阅读全文
posted @ 2011-08-09 20:54 永远的阿哲 阅读(522) 评论(0) 编辑
摘要: 现在业务系统的复杂程序越来越高,搞清楚其中的业务流程对于一个软件的成功至关重要,而一款好的业务流程软件则会令你事半功倍。 业务流程,在不同的业务层面会有不同的抽象程度,比如局领导就只会关心这个业务在几个科室间流转,最后的审批意件是什么,而具体的办事人员则更关心他办的这一步需要什么步骤。但是它们描述的都是同一个流程。给局领导看具体的业务图与给科员看很抽像的业务图都不合适。传统的业务流程软件并不能很好的表达这种层级的概念,只能画两个图,但是它们之间有重复的部份,然后以某种形式关联。但是XMind则很好的解决了这个问题。 XMind其实是一个思维导向图,主要记录一个人或一群人对于某件事务的思考过程.阅读全文
posted @ 2011-07-01 09:21 永远的阿哲 阅读(145) 评论(0) 编辑