Hello World
Spiga

我犯了一个错误,您能指出吗?

2009-09-08 10:01 by 老赵, 16697 visits

这是我最近在项目中犯的一个错误,您能指出吗?

这个项目在数据访问方面使用了传统的Repository模式。为此,我定义了一个Repository基类,可以让每个不同的Repository继承它:

public abstract class Repository<T> { ... }

public class UserRepository : Repository<User> { ... }

public class ArticleRepository : Repository<Article> { ... }

Repository类中提供了一些基础功能,可以让各个不同的Repository子类复用,这是十分常见的做法。其中一个功能便是资源的管理。在项目中,资源的管理通过Resource Manager进行——这不是.NET框架中自带的ResourceManager类,而是自己定义的组件:

public abstract class Repository<T>
{
    private IResourceManager ResourceManager { get { ... } }
}

public interface IResourceManager
{
    void Set(object key, object value);

    T Get<T>(object key);
}

被Resource Manager管理的资源会在一定时刻统一释放,而不同的IResourceManager对象的实现也有不同的“作用域”。例如,在Web项目中常见的做法是,对于每个数据库来说,每个请求使用一个连接。因此,这里使用的可能就是RequestResourceManager,它基于HttpReuqest提供资源存储,保证资源的作用域是当前请求。

数据库连接也是需要管理的资源之一,因此我在Repository中定义了一个GetConnection方法(假设只需要连接一个数据库):

public abstract class Repository<T>
{
    private readonly static Guid ConnectionKey = Guid.NewGuid();

    protected IDbConnection GetConnection()
    {
        var conn = this.ResourceManager.Get<IDbConnection>(ConnectionKey);
        if (conn == null)
        {
            conn = new SqlConnection("...");
            this.ResourceManager.Set(ConnectionKey, conn);
        }

        return conn;
    }
}

我在Repository类中定义了一个全局的ConnectionKey静态字段,并且在初始化时给它指定一个新的GUID。这个字段是readonly的,因此除非应用程序重启,ConnectionKey的值不会改变。而在GetConnection方法中,我把ConnectionKey作为标识,向Resource Manager中获取资源。从GetConnection方法的实现中可以看出,由于我们使用了RequestResourceManager,因此如果是同一个请求的话,就会共享相同的数据库连接对象。

于是,我们的代码里就可以这么写:

public void DoSomething()
{
    var userRepository = new UserRepository();
    // 使用userRepository

    var articleRepository = new ArticleRepository();
    // 使用articleRepository
}

在使用userRepository的时候,ResourceManager里还没有数据库连接,因此UserRepository会创建一个新的Connection对象。而在使用articleRepository的时候,它会发现ResouceManager中已经存在一个Connection对象了,于是便可取出继续使用。而这个连接会在请求结束时自动释放。

这就是我这个设计的目的。但是,在实现的过程中我犯了一个严重的错误,您发现了吗?(结论

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

115 条回复

  1. ??????[未注册用户]
    *.*.*.*
    链接

    ??????[未注册用户] 2009-09-08 10:16:00

    会有多个ConnectionKey,Repository<User>和Repository<Article>中的ConnectionKey不同?

  2. ideas
    *.*.*.*
    链接

    ideas 2009-09-08 10:17:00

    public readonly static Guid ConnectionKey = Guid.NewGuid();
    -----------------
    静态变量,且只读。在类被实体化之前被初始化,然后长驻内存,且为只读,这样所有请求都会共用这一个变量

  3. pk的眼泪
    *.*.*.*
    链接

    pk的眼泪 2009-09-08 10:18:00

    IDbConnection 长连接不合理,并发大数据量操作时会死掉的

  4. 狼Robot
    *.*.*.*
    链接

    狼Robot 2009-09-08 10:20:00

    每个T都会使用一个新的连接.

    泛型类中的静态变量会因为T的不同而产生不同的值,也就是说每个T所访问的静态变量都是独立的.

  5. cwbboy[未注册用户]
    *.*.*.*
    链接

    cwbboy[未注册用户] 2009-09-08 10:21:00

    大概看了下,

    两点:

    1、没看到你New ResourceManager的地方,你的Resourcemanager接口的实现类呢?

    2、你的 ResourceManager不是静态的,他只是基类的一个成员而已他不会在多个类之间共享,也就是说,你能共享的只是ConnectionKey

  6. cwbboy[未注册用户]
    *.*.*.*
    链接

    cwbboy[未注册用户] 2009-09-08 10:23:00

    ResourceManager无法共享,存储在里面的Connection就更加无法共享了,也就是说你无法实现你的原有目的。

  7. 李永京
    *.*.*.*
    链接

    李永京 2009-09-08 10:25:00

    private readonly IResourceManager _resourceManager;
    public Repository(IResourceManager resourceManager)
    {
        _resourceManager= resourceManager;
        //.....
    }
    

  8. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 10:25:00

    public abstract class Repository<T>
    {
        private IResourceManager ResourceManager
        {
            get
            {
                return null;
            }
        }
    }
    
    



    UserRepository和ArticleRepository中应该是两个实例
    按理说不会达到" 在使用userRepository的时候,ResourceManager里还没有数据库连接,因此UserRepository会创建一个新的Connection对象。而在使用articleRepository的时候,它会发现ResouceManager中已经存在一个Connection对象了,于是便可取出继续使用。"的效果

  9. 邀月
    *.*.*.*
    链接

    邀月 2009-09-08 10:25:00

    老赵,这句能编译通过吗?

    var conn = this.ResourceManager.Get<IDbConnection>(ConnectionKey);
            if (conn == null)
            {
                conn = new SqlConnection("...");
                this.ResourceManager.Set(ConnectionKey, conn);
            }
    
    

  10. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 10:31:00

    老赵你是不是再暗示

    由于泛型 <T> 是引用类型 所以多个
    Repository<User>
    Repository<Article>
    他们的 static 都指向同一个地址呢

    这个我得去试验下 嘿嘿

  11. hongzheng[未注册用户]
    *.*.*.*
    链接

    hongzheng[未注册用户] 2009-09-08 10:33:00

    我认为ResourceManager不是静态的,不会被userRepository和articleRepository共享,所以每个对象使用的都是自己的一份资源,没有达到共享的目的。

  12. 老赵
    admin
    链接

    老赵 2009-09-08 10:36:00

    cwbboy: ResourceManager无法共享,存储在里面的Connection就更加无法共享了,也就是说你无法实现你的原有目的。


    文章里已经写了:它基于HttpReuqest提供资源存储,保证资源的作用域是当前请求。

  13. 假正经哥哥
    *.*.*.*
    链接

    假正经哥哥 2009-09-08 10:38:00

    hongzheng:我认为ResourceManager不是静态的,不会被userRepository和articleRepository共享,所以每个对象使用的都是自己的一份资源,没有达到共享的目的。


    ResourceManager 不是静态的, 但是他的实现所使用的存储可能是唯一的比如HttpContext
    那么就算ResourceManager 不是静态也无所谓。

    我觉得问题在Key上,两个实例的Key不是相同的?赶紧去try 一下

  14. 老赵
    admin
    链接

    老赵 2009-09-08 10:38:00

    pk的眼泪:IDbConnection 长连接不合理,并发大数据量操作时会死掉的


    哪里长了,文章里不写着一个请求唯一一个连接吗?

  15. 老赵
    admin
    链接

    老赵 2009-09-08 10:40:00

    @hongzheng
    我觉得兄弟们的思路一定要改掉。
    就算ResourceManger不是静态的,但是多个不同的ResourceManager实例可以共享一个数据源啊。就像多个Connection实例不是也共享一个数据库吗?
    同理,多个线程也可以共享同一个实例,因此单个实例也会产生线程安全的问题。

  16. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 10:41:00

    
        class Program
        {
            static void Main(string[] args)
            {
    
    
                Console.WriteLine(test<Exception>.flag);
                System.Threading.Thread.Sleep(3000);
                        Console.WriteLine(test<FormatException>.flag);
                        Console.WriteLine(test<Exception>.flag);
                Console.Read();
                
            }
        }
    
        class test<T>
        {
    
            public static readonly  DateTime  flag =DateTime.Now  ;
    
        }
    


    三个时间完全一样

    静态构造函数只执行了一次?

  17. 老赵
    admin
    链接

    老赵 2009-09-08 10:41:00

    @韦恩卑鄙
    卑鄙兄威武。

  18. 老赵
    admin
    链接

    老赵 2009-09-08 10:41:00

    Ryan Gene:我的意思是new 了一个user1,connkey=1,再new一个user2,connkey=2,如果这时user1再拿connkey的话,估计就拿key2了,是不是这样呢?


    ConnectionKey是static的阿,怎么会和new出实例有关系呢?

  19. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-09-08 10:42:00

    还没看到下面,不过……

    public abstract class Repository<T>
    {
        private IResourceManager ResourceManager
        {
            get
            {
                return null;
            }
        }
    }
    

    这个是文中贴代码的时候手滑还是啥?private成员没办法override,ResourceManager总是返回null是预期行为么?

  20. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 10:45:00

    我是肥胖 -0-

        class Program
        {
            static void Main(string[] args)
            {
    
    
                Console.WriteLine(test<Exception>.flag);  //basetime
                System.Threading.Thread.Sleep(3000);
    
                test<Exception>.flag = DateTime.Now;  //basetime +3
                Console.WriteLine(test<FormatException>.flag); //basetime
                Console.WriteLine(test<Exception>.flag); //basetime +3
                Console.WriteLine(test<EventArgs>.flag); //basetime 
                Console.Read();
                
            }
        }
    
        class test<T>
        {
    
            public static   DateTime  flag =DateTime.Now  ;
    
        }
    

    这个代码比较有意思
    看起来构造函数仅仅运行了一次 但是地址确实是在不同地方的,似乎用了原形模式

  21. 老赵
    admin
    链接

    老赵 2009-09-08 11:01:00

    @DiggingDeeply
    asp.net的请求是单线程的。

  22. 杜建宇
    *.*.*.*
    链接

    杜建宇 2009-09-08 11:01:00


    成员变量应该私有。没必要开放。要开放加属性。

  23. 老赵
    admin
    链接

    老赵 2009-09-08 11:02:00

    @RednaxelaFX
    手滑,手滑,写“示例代码”时疏忽了……

  24. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-09-08 11:02:00

    韦恩卑鄙:
    我是肥胖 -0-
    这个代码比较有意思
    看起来构造函数仅仅运行了一次 但是地址确实是在不同地方的,似乎用了原形模式



    我以为恰恰相反 静态构造函数运行了3次 但它们修改了同一个变量

        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(test<Exception>.flag);  //basetime 
                System.Threading.Thread.Sleep(3000);
                test<Exception>.flag = DateTime.Now;  //basetime +3 
                Console.WriteLine(test<FormatException>.flag); //basetime 
                Console.WriteLine(test<Exception>.flag); //basetime +3 
                Console.WriteLine(test<EventArgs>.flag); //basetime  
                Console.Read();
            }
        }
        class test<T>
        {
            public static DateTime flag = DateTime.Now;
            static test()
            {
                Console.WriteLine("!"); 
            }
        }
    

  25. xing123[未注册用户]
    *.*.*.*
    链接

    xing123[未注册用户] 2009-09-08 11:08:00

    看了大家的评论,是不是这样
    UserRepository 与 ArticleRepository 类型不同,静态构造函数运行了2次,所以ConnectionKey值不同

  26. Funeral
    *.*.*.*
    链接

    Funeral 2009-09-08 11:11:00

    我觉得是这样的
    public void DoSomething()
    {
    var userRepository = new UserRepository();
    // 使用userRepository

    var articleRepository = new ArticleRepository();
    // 使用articleRepository
    }

    这段代码里两次实例化就会两次调用
    public readonly static Guid ConnectionKey = Guid.NewGuid();

    ConnectionKey就会有不同的值,就达不到Connection复用的目的.实际上两次new等于开了两个不同的Connection.

  27. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-08 11:14:00

    前面不是说一个用户请求用一个DbConnection吗?但看代码意思是每种Repository共用一个DbConnection,如果是这样,这种错误好像不应该。

  28. eEhdsa
    *.*.*.*
    链接

    eEhdsa 2009-09-08 11:18:00

    protected IDbConnection GetConnection()
        {
            var conn = this.ResourceManager.Get<IDbConnection>(ConnectionKey);   //Error will occur?
    

  29. 老赵
    admin
    链接

    老赵 2009-09-08 11:18:00

    @小城故事
    我的意图是:对于每个请求,所有的Repository公用一个DbConnection。
    估计“用户”两个字产生误导呢?我把它去了吧。

  30. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:19:00

    我好像跑题了

    我似乎还是没能完全理解老赵原来设计的要求

  31. 老赵
    admin
    链接

    老赵 2009-09-08 11:20:00

    @Funeral
    对象的实例化会改变静态字段的值吗?

  32. 老赵
    admin
    链接

    老赵 2009-09-08 11:22:00

    @韦恩卑鄙
    呀,难道我没有说清楚?不过最后的举例应该清楚了吧。
    设计的要求就是:对于单个请求,所有的Repository对象访问同一个Connection对象。

  33. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-09-08 11:22:00

    @DiggingDeeply
    并不是所有情况都共享方法的。
    范形参数都是引用类型的特化范型类型会共享方法的native代码,而范型参数是值类型的特化范型类型,每种特化都会有自己的一份方法代码。

  34. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-09-08 11:27:00

    记得以前谁讨论过这个问题的。。搜索记忆中

  35. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:28:00

    winter-cn:


    我以为恰恰相反 静态构造函数运行了3次 但它们修改了同一个变量


        class Program
        {
            static void Main(string[] args)
            {
    
                test<Exception>.flag = DateTime.Now;
                Console.WriteLine(test<Exception>.flag);  //basetime
                System.Threading.Thread.Sleep(3000);
    
                test<Exception>.flag = DateTime.Now;  //basetime +3
                Console.WriteLine(test<FormatException>.flag); //1900
                Console.WriteLine(test<Exception>.flag); //basetime +3
                Console.WriteLine(test<EventArgs>.flag); //2000 
                Console.Read();
                
            }
        }
    
        class test<T>
        {
    
            public static DateTime flag = GetTime();
                static DateTime GetTime()
                {
              
                    Console.WriteLine ("Visit");
                  return DateTime.Now ;
                }
    
        }
    


    果然访问了三次

    就算时间一样 三次guid 也是不同的 那么老赵一个请求共用一个key的要求就无法达到了

  36. James.Ying
    *.*.*.*
    链接

    James.Ying 2009-09-08 11:33:00

    好多高人啊

  37. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:34:00

       class Program
        {
            static void Main(string[] args)
            {
    
    
                Console.WriteLine(test<Exception>.flag);  //basetime
                System.Threading.Thread.Sleep(3000);
    
                test<Exception>.flag = DateTime.Now;  //basetime +3
                Console.WriteLine(test<FormatException>.flag); //basetime
                Console.WriteLine(test<Exception>.flag); //basetime +3
                Console.WriteLine(test<EventArgs>.flag); //basetime 
                Console.Read();
                
            }
        }
    
        class test<T>
        {
    
            public static   DateTime  flag =DateTime.Now  ;
    
        }
    


    这段代码能否理解为datetime类型做了缓存?
    不然为什么test<EventArgs>的flag也是basetime?

    为什么 datetime 能够缓存结果 而guid不能聂?

  38. 老赵
    admin
    链接

    老赵 2009-09-08 11:39:00

    @韦恩卑鄙
    这是从表面上无法没有分析出来的,呵呵。我给一个case,例如这样的代码:

    public class MyClass<T>
    {
        public static readonly DateTime Time = DateTime.Now;
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("int: " + MyClass<int>.Time);
            Thread.Sleep(3000);
            Console.WriteLine("string: " + MyClass<string>.Time);
            Console.WriteLine("int: " + MyClass<int>.Time);
    
            Console.ReadLine();
        }
    }
    
    这样便会打印出三个相同的时间,但是如果我加一个静态构造函数:

    public class MyClass<T>
    {
        public static readonly DateTime Time = DateTime.Now;
    
        static MyClass() { } // haha
    }
    
    一切就大不同了……
    C#代码看不出意外,但是结果不同,呵呵。

  39. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:42:00

    找到问题了

        class test<T>
        {
            
            public static   DateTime  flag =gettime ()  ;
            static DateTime gettime()
            {
                Console.WriteLine("visit");
                return DateTime.Now;
                
            }
        }
    


    visit
    visit
    visit
    2009-9-8 11:38:41
    2009-9-8 11:38:41
    2009-9-8 11:38:44
    2009-9-8 11:38:41


    在确认代码里面会访问
    <Exception>
    <FormatException>
    <EventArgs>
    的时候

    JIT会先运行所有这些类的静态构造函数 然后再运行后面的代码

    所以给我一种 datetime被缓存的错觉

  40. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-08 11:43:00

    这个ResourceManager思路得学习,用到项目中。

  41. HCOONa
    *.*.*.*
    链接

    HCOONa 2009-09-08 11:45:00

    很奇怪的结论,路过,继续关注

  42. 老赵
    admin
    链接

    老赵 2009-09-08 11:45:00

    @韦恩卑鄙
    但是,你手动加了静态构造函数后就都不一样了。
    下午我把你的这些结论也一起整理一下发布一下,打个招呼,嗯嗯。

  43. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:46:00

    @Jeffrey Zhao
    等你的完全研究报告 我也得去吃午饭了 :D

    难得今天没有工作嘿嘿嘿

  44. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:48:00

    果然大不一样 结果也大不一样

        class Program
        {
            static void Main(string[] args)
            {
    
    
                Console.WriteLine(test<Exception>.flag);  //basetime visit
                System.Threading.Thread.Sleep(3000);
    
                test<Exception>.flag = DateTime.Now;  //basetime +3
                Console.WriteLine(test<FormatException>.flag); //basetime+3 visit
                Console.WriteLine(test<Exception>.flag); //basetime +3
                Console.WriteLine(test<EventArgs>.flag); //basetime +3 visit
                Console.Read();
                
            }
        }
    
        class test<T>
        {
            
            public static   DateTime  flag =gettime ()  ;
            static DateTime gettime()
            {
                Console.WriteLine("visit");
                return DateTime.Now;
                
            }
    
            static test()
            {
                Console.WriteLine("visit!");
            }
        }
    


    visit
    visit!
    2009-9-8 11:48:13
    visit
    visit!
    2009-9-8 11:48:16
    2009-9-8 11:48:16
    visit
    visit!
    2009-9-8 11:48:16

  45. WilsonWu
    *.*.*.*
    链接

    WilsonWu 2009-09-08 11:49:00

    赵兄的博客模板真是不错。

  46. 老赵
    admin
    链接

    老赵 2009-09-08 11:49:00

    @韦恩卑鄙
    静态构造函数留空就可以了,不需要做任何东西。

  47. 老赵
    admin
    链接

    老赵 2009-09-08 11:49:00

    @韦恩卑鄙
    嘿嘿,你哪天有工作啊。

  48. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 11:51:00

    @Jeffrey Zhao
    哪天看我不刷你版 八成就是有任务了

  49. 戏水
    *.*.*.*
    链接

    戏水 2009-09-08 11:56:00

    C#的定义中指出当只有在一个类型不具备静态构造器时它的BeforeFieldInit才会被自动标记上。事实上,这是由编译器帮我们完成的,它可能会导致一些我们意想不到的效果。

    最后我要再次强调,静态构造器并不等同于类型初始化器。任何类型都有类型初始化器,但不一定有静态构造器

  50. 小眼睛老鼠
    *.*.*.*
    链接

    小眼睛老鼠 2009-09-08 11:57:00

    protected IDbConnection GetConnection()
        {
            var conn = this.ResourceManager.Get<IDbConnection>(ConnectionKey);
            if (conn == null)
            {
                conn = new SqlConnection("...");
                this.ResourceManager.Set(ConnectionKey, conn);
            }
    
            return conn;
        }
    
    

    这里没加锁 第一次的时候可能存在并发问题

  51. 梦碎无痕[未注册用户]
    *.*.*.*
    链接

    梦碎无痕[未注册用户] 2009-09-08 11:59:00

    声明了静态变量而没有静态构造函数的,静态初始化将保证在访问任何静态字段之前的某个时刻发生,在CLR via C#里面就有举过这样的一个列子,没有静态构造函数的时候,静态初始化在刚进入方法的时候就发生了,而声明静态构造函数后,在使用前才发生。

  52. 老赵
    admin
    链接

    老赵 2009-09-08 12:01:00

    @戏水
    @梦碎无痕
    兄弟们说的好啊。

  53. 心利
    *.*.*.*
    链接

    心利 2009-09-08 12:04:00

    @韦恩卑鄙

    test<Exception>,test<EventArgs>,test<Exception>
    

    这个三个类型的静态属性应该是同一个时间(某毫秒)初始化的,所以导致datetime.now几乎是一样的。

  54. 戏水
    *.*.*.*
    链接

    戏水 2009-09-08 12:11:00

    http://www.cnblogs.com/artech/archive/2008/11/01/1324280.html
    这个文章 解释了这个问题吧 呵呵

  55. 心利
    *.*.*.*
    链接

    心利 2009-09-08 12:11:00

    刷的好快,汗一个……

  56. 老赵
    admin
    链接

    老赵 2009-09-08 12:13:00

    @戏水
    谢了,一会儿直接引用它,省事多了,呵呵。

  57. 涤生
    *.*.*.*
    链接

    涤生 2009-09-08 12:17:00

    五楼正解

    每个T都会使用一个新的连接.

    泛型类中的静态变量静在相同封闭类间共享,不同的封闭类间不共享。因此会因为T的不同而产生不同的值,也就是说每个T所访问的静态变量都是独立的.

  58. TOOTH[未注册用户]
    *.*.*.*
    链接

    TOOTH[未注册用户] 2009-09-08 12:22:00

    刚才写了代码验证了一下,确实是泛型的问题导致的,实例化不同的泛型参数后,估计静态成员变量存放不同的地址了。改为非泛型后,发现key的值就一致了。那么既然是泛型的问题,不知道楼主的ResourceManage怎么保持其的一致性,如果可以保证RM的一致性,那么同理的方法将Key处理一下就OK了,所以最终楼主的解决方法是....

  59. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-09-08 12:26:00

    @Jeffrey Zhao


    貌似加不加空的静态构造函数,3个特化test类的flag都不同。

        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(test<Exception>.flag);  //basetime visit
                Console.WriteLine(test<FormatException>.flag); 
                Console.WriteLine(test<EventArgs>.flag); 
                Console.Read();
    
            }
        }
    
        class test<T>
        {
            public readonly static Guid flag = Guid.NewGuid();
            
            static test()
            {
                //Console.WriteLine("visit!");
            }
        }
    

  60. 老赵
    admin
    链接

    老赵 2009-09-08 12:27:00

    @iceboundrock
    无论加不加,这里都不同,因为这里的问题和“执行时机”是无关的。:)

  61. 老赵
    admin
    链接

    老赵 2009-09-08 12:28:00

    @TOOTH
    RM和ConnectionKey的问题是无关的,解决方案也没有任何关系,这里RM是没有问题的。

  62. 鹤冲天
    *.*.*.*
    链接

    鹤冲天 2009-09-08 12:31:00

    又来晚了,不过已经回答了:泛型类为每个型都保存一组静态字段和属性。
    Repository<User>、Repository<Article>各自具有一个ResourceManager 。

  63. 老赵
    admin
    链接

    老赵 2009-09-08 12:33:00

    @鹤冲天
    字段归字段,和属性没有关系,属性只是方法而已。
    对于方法,泛型类为引用类型共享一份native code,为每个值类型分别生成一份的native code。

  64. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-09-08 12:37:00

    呃,,,,其实老赵已经明示了,如果我没猜错的话,这个问题应该在装配脑袋的博客上有答案。

  65. 鹤冲天
    *.*.*.*
    链接

    鹤冲天 2009-09-08 12:42:00

    @Jeffrey Zhao
    老赵回复好快,正在修改回复中...
    应该是泛型类为每个型都保存一组静态字段,Repository<User>、Repository<Article>各自具有一个ConnectionKey,它们的值是不相同的。(感觉表达还是不到位)

  66. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-08 12:54:00

    每个闭合的泛型类型的静态字段是独立的,这个脑袋很早以前写过文章

    老赵你所谓的错误我觉得就是这个,跟评论里提到的静态构造函数看上去没什么关系啊

  67. Galactica
    *.*.*.*
    链接

    Galactica 2009-09-08 12:59:00

    public abstract class RepositoryBase { private readonly static Guid ConnectionKey = Guid.NewGuid();
    ... }

    public abstract class Repository<T> : RepositoryBase { ... }

    Repository<T> 不是类型,不能够实例,它只是模板,Repository<User>、Repository<Article并不是"继承"Responsitory<T>,不是子类,而是两个新的类.

  68. 老赵
    admin
    链接

    老赵 2009-09-08 13:02:00

    @麒麟.NET
    你说的没错,后面是歪楼了,呵呵。

  69. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 13:04:00

    Jeffrey Zhao:
    @麒麟.NET
    你说的没错,后面是歪楼了,呵呵。


    都怪我的实验不科学

  70. 老赵
    admin
    链接

    老赵 2009-09-08 13:07:00

    @韦恩卑鄙
    我觉得其实如果有人能安心看完这些回复,已经了解的差不多了,要不要总结呢……

  71. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-08 13:07:00

    @韦恩卑鄙
    我看到后面把自己的评论都删了,刚才吃饭的时候还猛想cctor。。。。
    debug一把看汇编,就发现不是同一个地址了,还扯了半天的datetime,不过还得谢谢你贡献测试代码,虽然歪了点。

  72. 在别处
    *.*.*.*
    链接

    在别处 2009-09-08 13:10:00

    泛型类的静态变量都是独立的吧

  73. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-08 13:12:00

    @Jeffrey Zhao
    还是总结一下吧,不然新来的又的兜一圈。

    老赵你12点半去吃饭,1点就回来啦,你吃的啥啊?这么快?

  74. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 13:17:00

    不要删除么  大家的思考过程进化都有意义的

  75. 老赵
    admin
    链接

    老赵 2009-09-08 13:29:00

    @韦恩卑鄙
    这我同意卑鄙兄的

  76. 老赵
    admin
    链接

    老赵 2009-09-08 13:29:00

    @DiggingDeeply
    楼下食堂,去了就打饭,吃完就回来。

  77. 小彬
    *.*.*.*
    链接

    小彬 2009-09-08 13:30:00

    看似一样的代码

    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(Demo1<int>.Time);
                Sleep();
                Console.WriteLine(Demo1<float>.Time);
                Sleep();
                Console.WriteLine(Demo1<string>.Time);
                Sleep();
                Console.WriteLine(Demo1<Program>.Time);
                Sleep();
                Console.WriteLine(Demo1<int>.Time);
                Sleep();
                Console.WriteLine();
    
                Console.WriteLine(Demo2<int>.Time);
                Sleep();
                Console.WriteLine(Demo2<float>.Time);
                Sleep();
                Console.WriteLine(Demo2<string>.Time);
                Sleep();
                Console.WriteLine(Demo2<Program>.Time);
                Sleep();
                Console.WriteLine(Demo2<int>.Time);
                Sleep();
                Console.WriteLine();
    
                WriteSth<int>();
                WriteSth<float>();
                WriteSth<string>();
                WriteSth<Program>();
                WriteSth<int>();
            }
    
            private static void WriteSth<T>()
            {
                Console.WriteLine(Demo3<T>.Time);
                Sleep();
            }
    
            public static void Sleep()
            {
                Thread.Sleep(1000);
            }
        }
    
        class Demo1<T>
        {
            public static readonly DateTime Time = DateTime.Now;
        }
    
        class Demo2<T>
        {
            public static readonly DateTime Time = DateTime.Now;
    
            static Demo2()
            {
    
            }
        }
    
        class Demo3<T>
        {
            public static readonly DateTime Time = DateTime.Now;
        }
    }
    

  78. 鸟窝[未注册用户]
    *.*.*.*
    链接

    鸟窝[未注册用户] 2009-09-08 13:30:00

    加了static constructors, static field会初始化多次。
    反编译一下可以看到。

    class Program
    {
    static void Main(string[] args)
    {
    Foo<int>.PrintValue();

    Foo<string>.PrintValue();

    Console.Read();
    }
    }

    public class Foo<T>
    {

    public static readonly long Value = DateTime.Now.Ticks;

    static Foo()
    {

    Value = DateTime.Now.Ticks;

    }

    public static void PrintValue()
    {

    Console.WriteLine(Value);

    }

    }

    反编译后:

    static Foo()
    {
    Foo<T>.Value = DateTime.Now.Ticks;
    Foo<T>.Value = DateTime.Now.Ticks;
    }



  79. 小彬
    *.*.*.*
    链接

    小彬 2009-09-08 13:32:00

    运行结果
    2009-9-8 13:32:08
    2009-9-8 13:32:08
    2009-9-8 13:32:08
    2009-9-8 13:32:08
    2009-9-8 13:32:08

    2009-9-8 13:32:13
    2009-9-8 13:32:14
    2009-9-8 13:32:15
    2009-9-8 13:32:16
    2009-9-8 13:32:13

    2009-9-8 13:32:18
    2009-9-8 13:32:19
    2009-9-8 13:32:20
    2009-9-8 13:32:21
    2009-9-8 13:32:18
    请按任意键继续. . .

  80. 老赵
    admin
    链接

    老赵 2009-09-08 13:34:00

    鸟窝:
    加了static constructors, static field会初始化多次。
    反编译一下可以看到。


    那是你在static constructor外面初始化了一次,里面又初始化了一次,当然初始化了两次。
    这个和有没有static constructor导致是否有beforefieldinit没有关系。

  81. 老赵
    admin
    链接

    老赵 2009-09-08 13:34:00

    @小彬
    好复杂,一会儿我还是总结一个简单又能说明问题的例子吧。

  82. 匿名用户A[未注册用户]
    *.*.*.*
    链接

    匿名用户A[未注册用户] 2009-09-08 13:35:00

    @小彬
    DateTime不足以区分吧,时间是毫秒级别的

  83. 鸟窝[未注册用户]
    *.*.*.*
    链接

    鸟窝[未注册用户] 2009-09-08 13:41:00

    @Jeffrey Zhao
    yes. u r right.

    普及一下答案吧

  84. 鸟窝[未注册用户]
    *.*.*.*
    链接

    鸟窝[未注册用户] 2009-09-08 13:44:00

    我运行我那个例子
    如果加static constructors,两个static field的值是一样。
    不加则值不同。

    难道所有的generi公用一个static constructor?

  85. 鸟窝[未注册用户]
    *.*.*.*
    链接

    鸟窝[未注册用户] 2009-09-08 13:48:00

    @鸟窝
    :( 描述错了

    加static constructors,两个static field的值是不一样。

  86. 鸟窝[未注册用户]
    *.*.*.*
    链接

    鸟窝[未注册用户] 2009-09-08 13:50:00

    算了。我越运行越乱。大家无视我的描述吧。:(

  87. 老赵
    admin
    链接

    老赵 2009-09-08 13:52:00

    @鸟窝
    呵呵,所以保持示例简单是多么重要啊。

  88. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-08 13:58:00

    @鸟窝
    看看这位兄弟,进化的糊涂了吧。
    这里加不加cctor,都和本文的问题没有关系。
    先说cctor,这个加不加的区别就是静态字段的初始化时序问题,不加是调用前初始化,加是调用时再去初始化。
    而老赵的文章遇到的问题是泛型类的静态字段问题,上面老赵说了,静态字段是每类型一份;方法是因泛型类型T而异,所有引用共享一份,而值类型则单独各一份。

    不知道说的明白不?开会去~~~

    老赵赶紧公布答案,我想知道你是怎么解决这个问题的,我想到的方案是 singleton connection,但是这么做有违你请求周期内释放的要求,别和我一样啊。

  89. 小彬
    *.*.*.*
    链接

    小彬 2009-09-08 14:00:00

    @Jeffrey Zhao
    不复杂吧?

    第一个,没有 static 构造函数,结果出来的值是一样的(注意:两个调用之间有Sleep 1秒钟)

    第二个,有static 构造函数,每个不同的类型,结果不一样

    第三个,是我在测试的时候,因为想重构一下,提取一个泛型函数偶然发现的,结果也不一样

  90. 老赵
    admin
    链接

    老赵 2009-09-08 14:00:00

    @DiggingDeeply
    寒……不要让ConnectionKey等于Guid.NewGuid()不就可以了么,用固定的,比如new Guid("...")……

  91. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-09-08 14:10:00

    public abstract class Repository<T>
    {
    private readonly static Guid ConnectionKey = Guid.NewGuid();
    ...
    }
    不同的T会有不同的ConnetionKey,所以

    var userRepository = new UserRepository();
    var articleRepository = new ArticleRepository();

    并不能共享同一个ConnetionKey.

    我以前写过一个类:AssemblyFactory<T> 来加载实例,所以明白其中道理,呵呵.

  92. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-08 14:13:00

    不要被DateTime迷惑了双眼,其实这里用Guid是最好的,用DateTime的话如果直接Console.Write,出来的时间只精确到秒,而不同的闭合类型的静态字段又几乎是同一时间初始化的(BeforeFieldInit的情况下),因此看上去好像是多个闭合类型的静态字段值一样似的,其实完全不是这么回事。

  93. 老赵
    admin
    链接

    老赵 2009-09-08 14:16:00

    @姜敏
    每请求一个数据连接是非常常用的做法,它的好处之一就是——不会出现分布式事务,呵呵。
    与它类似的实践还有,NHiberante提倡每请求一个Session,但是我认为这么做有缺陷,因此我选择多Session但是单Connection。

  94. 小彬
    *.*.*.*
    链接

    小彬 2009-09-08 14:22:00

    @麒麟.NET
    你说的“闭合类型”是什么样的类型?

  95. 姜敏
    *.*.*.*
    链接

    姜敏 2009-09-08 14:22:00

    我的问题怎么删除了?

  96. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-08 14:26:00

    @小彬
    闭合的泛型类型,即Repository<User>这样的类型

  97. 老赵
    admin
    链接

    老赵 2009-09-08 14:28:00

    @姜敏
    你发了三篇一样的,我删了两篇,是不是另一篇你自己删了?

  98. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-08 14:38:00

    Jeffrey Zhao:
    @姜敏
    每请求一个数据连接是非常常用的做法,它的好处之一就是——不会出现分布式事务,呵呵。
    与它类似的实践还有,NHiberante提倡每请求一个Session,但是我认为这么做有缺陷,因此我选择多Session但是单Connection。


    这个我比较倾向自定义一个 里面带有 connection 的trans scrope阿 :P

  99. pk的眼泪
    *.*.*.*
    链接

    pk的眼泪 2009-09-08 14:56:00

    @Jeffrey Zhao
    我错了,看完评论确实收获不少哈。

  100. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-08 15:18:00

    @Jeffrey Zhao
    汗,那不就写死了吗?hard code了。

  101. 老赵
    admin
    链接

    老赵 2009-09-08 15:24:00

    @DiggingDeeply
    不过没有问题啊,当然你也可以放在一个非范型的类中,就OK了。

  102. 大白
    *.*.*.*
    链接

    大白 2009-09-08 15:26:00

    public abstract class Repository<T>
    {
    private readonly static Guid ConnectionKey = Guid.NewGuid();

    protected IDbConnection GetConnection()
    {
    var conn = this.ResourceManager.Get<IDbConnection>(ConnectionKey);
    if (conn == null)
    {
    conn = new SqlConnection("...");
    this.ResourceManager.Set(ConnectionKey, conn);
    }

    return conn;
    }
    }
    老赵,你给的代码量太少了,没看出你测试的代码,看不出什么。但是,单单从上面的意思看,基本满足不了你的要求。因为,泛型!也就是说,你上面其实定义了三种类型。而三种类型,实现了三个不相关的静态ConnectionKey,而你的ResourceManager和其方法,却是实例属性。所以,上面的结果会是:每种类型,使用一个连接。而你的Repository<T>,有多少种不同的类型,就会有多少个连接。
    除非guid重复。不知道我说的对不对。

  103. 老赵
    admin
    链接

    老赵 2009-09-08 15:26:00

    @韦恩卑鄙
    嗯嗯,把Connection的生命周期放在TransactionScope里也是可以的啊。

  104. 看看看看[未注册用户]
    *.*.*.*
    链接

    看看看看[未注册用户] 2009-09-08 16:14:00

    我感觉 不是 老赵错了,只是在设计上没有达到他想向的要求,老赵对吗?

  105. 老赵
    admin
    链接

    老赵 2009-09-08 16:16:00

    @看看看看
    设计是对的,实现错了。

  106. 看看看看[未注册用户]
    *.*.*.*
    链接

    看看看看[未注册用户] 2009-09-08 16:26:00

    private readonly static Guid ConnectionKey = Guid.NewGuid();
    你的设计是认为 每一个实例 的 ConnectionKey 是相同的,但因为用了范型在实际的使用中 每个实例 的ConnectionKey 都不一样,这样造成你的设计和实际应用不一至 了,不知我说的对不对?

  107. 路人甲1[未注册用户]
    *.*.*.*
    链接

    路人甲1[未注册用户] 2009-09-08 20:02:00

    共用一个connection对象,可能第二次用着用着发现对象没了。
    乱扯一下

  108. m j[未注册用户]
    *.*.*.*
    链接

    m j[未注册用户] 2009-09-08 20:11:00

    好不容易把评论看完了。。老赵的文章果然火。。

    不过评论好像有点问题。。怎么我这里看到一共有两页,第一页只有9楼,第二页就到了115楼。。是老赵你模板的问题还是博客园的问题?

  109. 老赵
    admin
    链接

    老赵 2009-09-08 20:13:00

    @m j
    我觉得这个评论机制很合理,就是一进页面让你看最新的100条评论啊。
    第1页是较早的评论,自然少了。

  110. chinarenkai
    *.*.*.*
    链接

    chinarenkai 2009-09-08 21:29:00

    技术不行。实在看不懂

  111. EricWen
    *.*.*.*
    链接

    EricWen 2009-09-09 10:32:00

    简单问题复杂化了。
    楼上的这句话:
    Repository<T> 不是类型,不能够实例,它只是模板,Repository<User>、Repository<Article并不是"继承"Responsitory<T>,不是子类,而是两个新的类.
    足以说明问题了。
    什么静态构造函数啊,都跟这没有关系。

  112. 老赵
    admin
    链接

    老赵 2009-09-09 10:34:00

    @EricWen
    后面是楼歪了,没在说同一个问题。

  113. xgd224743[未注册用户]
    *.*.*.*
    链接

    xgd224743[未注册用户] 2009-09-10 13:35:00

    应为采用了范泛.每一次传入的T不一样..所以每一次的GUID生成的都不一样

  114. 不明白
    116.228.220.*
    链接

    不明白 2011-01-30 12:12:17

    怎么写的不在了, 我是想问:缓存IDbConnection的作用是不是同一访问,多次访问数据库用相同的IDbConnection,为的就是需要再次new一次,节省资源吗?

  115. 老赵
    admin
    链接

    老赵 2011-01-30 17:50:52

    @不明白

    SqlConnection自身带有连接池,所以这方面的性能倒不是问题。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我