# Monday, July 07, 2008

    近期在对一些客户做技术培训的时候,发现了一个问题。一些我们平常天天在用的软件工具,比如:Windows、Office、SQL Server、Visual Studio等等,很多都没有得到用户较充分的使用。很多已经被软件厂商早已攻克的问题,却在用户那里在被重复研究解决方案,原因为什么?因为用户根本不知道原来这个每天都在用的软件工具还有这样或那样的功能,可以解决我们目前所碰到的问题。比如说:

  • Excel就已经可以轻松解决家庭的收支结算问题,有人却偏偏要安装什么管家婆或者形形色色的所谓家庭财务管理软件,甚至还要求你安装个sql server呢。难道你只把excel当作一个普通表格使用吗?
  • 不少人抱怨Word排版很差,对文章分层次时,要手工敲入那些“第一、第二”或者“1)、2)”等,为什么不用编号功能呢?
  • sql server 2005 有个“数据库引擎优化顾问”,可以在一定程度上帮助优化数据库,而对应于sql 2000中也存在“索引优化向导”,而他们都依赖于sql profile,另外数据库有执行计划等一系列分析优化的工具,为什么连这些都没用过就问人索要什么优化工具呢?
  • Windows自身就带有磁盘清理等系统优化工具,为啥你只为了清理一下系统盘的空间,却要安装“超级**”或是“**优化大师”呢?

    上述都只是一些简单的例子,但在相当多用户中存在类似的问题,而在软件开发人员中问题依然不少见。问题出在哪里,用户自身是问题之一,而作为软件提供商来说,是否应该考虑一下自身的问题,是否应该下一下功夫对用户进行培训教育呢?UI是否可以设计的再合理一些,让用户更直接的了解到软件的已有功能呢?

     正如某些分析师说的:“微软的Windows、Word和Excel等产品中功能丰富,但消费者却并不完全了解.其结果就是,消费者耗费大量的时间来解决一些微软产品已经攻克的问题.因此,鲍尔默应该在该领域投入一笔资金,以帮助用户了解微软产品的功能”。个人认为这样这个问题不光是微软应该考虑的问题,IBM、Oracle以及我们国内的金山等软件企业是否也应该思考呢?共享软件作者亦应如此。

posted on Monday, July 07, 2008 5:22:05 PM (China Standard Time, UTC+08:00)  #    Comments [0]
# Tuesday, November 27, 2007

在一些网站或者软件系统中,由于安全或者程序异常等问题,可能会在SQL Server 2000中造成一个异常问题:在企业管理器中打开数据库发现有一些“坏表”,即无法使用,也无法正常删除。其原因往往是因为在尝试建表的过程中,只向数据库服务器的系统表sysobjects中写入了类型为“U”的记录,却并未真正在数据库中成功创建该表的实体。ok,解决问题有思路了:

假设“坏表”的表名为cmd,那么我们在查询分析器中对当前的数据库写一个sql语句:

delete from sysobjects where [name]='cmd' and xtype='U'

按F5执行,可能会碰到一个错误提示:“未启用对系统目录的特殊更新。系统管理员必须重新配置 SQL Server 以允许这种操作。” 解决这个小小的问题的方法是:打开mssql server 属性---》服务器选项----》启用对系统目录的特殊更新(允许对系统目录进行直接修改)  即可。之后重新在查询分析器中执行刚才的sql语句。

执行成功后。在企业管理器中会发现那个坏表已经不存在了。

[ 注:SQL2005中,由于微软做了重大调整,正常情况下不允许对系统表进行即席更新操作。所以本文提到的问题目前仅适用于SQL2000数据库,敬请注意 ]

posted on Tuesday, November 27, 2007 11:35:54 PM (China Standard Time, UTC+08:00)  #    Comments [0]
# Wednesday, June 13, 2007

asp.net 2.0中引入的Membership机制给我们做web开发带来很多便利,使得我们在做很多项目开发时处理用户和角色及权限问题时减少了相当的工作量。不过我们开发中可能会碰到一个小小的问题如下:

我们知道MembershipUser中有如下重载的方法

  • MembershipUser.ResetPassword () :将用户密码重置为一个自动生成的新密码。
  • MembershipUser.ResetPassword (String passwordAnswer) :将用户密码重置为一个自动生成的新密码。

msdn中有如下描述:

ResetPassword 调用 ProviderName 属性所引用的成员资格提供程序的 MembershipProvider.ResetPassword 方法,以将成员资格用户的密码重置为自动生成的新密码。然后将该新密码返回到调用方。

如果 EnablePasswordReset 为 false,则成员资格提供程序将返回一个异常。

如果 RequiresQuestionAndAnswer 为 true,则必须使用以密码提示问题答案作为参数的 ResetPassword 重载方法,并提供成员资格用户的密码。如果需要密码答案并提供了不正确的密码答案,成员资格提供程序将引发 MembershipPasswordException。

而另外一个方法MembershipUser.ChangePassword(String oldPassword, String newPassword) 又必须提供原始密码,那么这里就产生了一个矛盾:我们希望用户能够通过安全问题和答案来作为找回密码,又希望管理员可以重置某用户的密码怎么办?上述方法均无法直接使用,要么使用MembershipUser.ResetPassword ()而无安全问题和答案验证,要么必须知道问题答案或者原始密码,我们知道这些安全相关的东西我们都是加密的,而且往往采用单向编码的方式,也是说我们不可能轻易的通过读取数据库数据知道用户的答案和旧密码!那么好,如何解决这个问题呢?下面我来提供一个小的解决方案:

Membership的数据库中包含有一个设置用户密码的存储过程:

CREATE PROCEDURE aspnet_Membership_SetPassword
(

 @ApplicationName nvarchar(256),

 @UserName nvarchar(256),

 @NewPassword nvarchar(128),

 @PasswordSalt nvarchar(128),

 @CurrentTimeUtc datetime,

 @PasswordFormat int = 0
)

返回值:成功返回 0;
用户不存在,返回 1;

那么ok,我们开始解决我们的需求吧!思路很简单我们先设置用户一个默认的密码,然后以该密码为旧密码使用MembershipUser.ChangePassword(String oldPassword, String newPassword) 修改用户的密码。下面的代码是在以DataAccess.RunProcedure方法是已经实现的执行存储过程的方法为前提编写的:

public class MembershipSafe
 {
     public static string ResetPassword(string username)
     {
         //将用户密码修改为8位且包含一个特殊符号的随机密码
         return ResetPassword(username, Membership.GeneratePassword(8,1));
    }
  
     public static string ResetPassword(string username, string newpassword)
     {
         //先将用户密码修改为123123
  
         SqlParameter[] _sp = {
                              new SqlParameter("@ApplicationName",Membership.ApplicationName)
                             ,new SqlParameter("@UserName",username)
                             ,new SqlParameter("@NewPassword","1M4h3eZLAKW1WbVTTwYJiJZa33w=")
                             ,new SqlParameter("@PasswordSalt","rcVy3pCccZ9tXW7NHp1Maw==")
                             ,new SqlParameter("@CurrentTimeUtc",DateTime.Now)
                             ,new SqlParameter("@PasswordFormat",1)
         };
         bool op = DataAccess.RunProcedure("aspnet_Membership_SetPassword", _sp) == 0;
         //以123123为原始密码,修改为新密码
         Membership.GetUser(username).ChangePassword("123123", newpassword);
         return newpassword;
     }
 }

注:以上代码仅做参考,不一定是符合您具体项目的需求,比如有人可能会继续问修改了密码怎么告诉用户啊,答案是发邮件或者电话通知啊,哈哈~~

推荐浏览:

posted on Wednesday, June 13, 2007 2:41:02 PM (China Standard Time, UTC+08:00)  #    Comments [0]
# Friday, April 06, 2007

数据库镜像是SQL Server 2005的一个新特性,它允许你将一个SQL Server中的数据库内容镜像到另一个SQL Server上。它还让你可以在发生错误的时候,通过镜像数据库来进行错误恢复。镜像的拷贝是一个备用的拷贝,不能直接访问,它只用在错误恢复的情况下。SQL2005的这一特性给数据库系统维护人员带来了不错的实惠,而给最终用户的体验也是不错的。至于如何配置数据库镜像系统,大家可以到TechNet 中文网络广播 中查找一下相关视频教程,尤其推荐大家去看看MVP牛可的相关讲座视频哦。再这里就不多说了。我们直奔主题吧!!

在使用数据库镜像功能时,如果我们使用的是工作组,那么我们需要注意一下数据库用户的问题:用普通方式创建的数据库用户,在主数据库中和镜像数据库中是有一些差异的,而这个差异最终会造成一个“奇怪”的结果,我们在主数据库和镜像数据库中对某数据库影射的“相同用户”,在发生故障转移后数据库验证会发生异常,称该用户无法登录我们的数据库,WHY? 原来是两个数据库中的同名用户在系统表syslogins中的sid不同造成的。解决方法就是在两个数据库上建立同名同sid的用户,具体方法如下:

假设有镜像的服务器是MIR-A和MI-B,要访问镜像数据库的登录账号是tom,使用下面这个方法就可以在MIR-A上创建登录名tom,然后在镜像数据库中创建数据库用户tom并映射到登录名tom
执行下列语句取回tom的sid

select [name], sid from syslogins where [name] = 'tom'
------  -----------------------------------------------
tom 0xD6AABCC8F83E3243A6C3C97F28A4CB55

然后在MIR-B上用下面的语句创建登录名tom

sp_addlogin @loginame = 'tom', @passwd = 'password', @sid = 0xD6AABCC8F83E3243A6C3C97F28A4CB55
Ok,这样就没有问题了。
posted on Friday, April 06, 2007 9:51:10 PM (China Standard Time, UTC+08:00)  #    Comments [1]
# Friday, June 02, 2006
虽然 Apache 的名声可能比 IIS 好,但我相信用 IIS 来做 Web 服务器的人一定也不少。说实话,我觉得 IIS 还是不错的,尤其是 Windows 2003 的 IIS 6(马上 Longhorn Server 的 IIS 7 也就要来了,相信会更好),性能和稳定性都相当不错。但是我发现许多用 IIS 的人不太会设置 Web 服务器的权限,因此,出现漏洞被人黑掉也就不足为奇了。但我们不应该把这归咎于 IIS 的不安全。如果对站点的每个目录都配以正确的权限,出现漏洞被人黑掉的机会还是很小的(Web 应用程序本身有问题和通过其它方式入侵黑掉服务器的除外)。下面是我在配置过程中总结的一些经验,希望对大家有所帮助。
posted on Friday, June 02, 2006 1:13:14 PM (China Standard Time, UTC+08:00)  #    Comments [0]
# Thursday, January 19, 2006

宝玉的Blog上看到了名为《Sql2005带来的分页便利》的短文,文章很短,但是却让我欣喜万分,MS终于在SQL Server上给解决了大量数据的分页问题了。以前我经常羡慕MySql用户可以使用很简单的办法来处理分页问题,而今天我们在Sql2005上也同样可以很方便的处理分页问题了。

select threadid from
(
select threadid, ROW_NUMBER() OVER (order by stickydate) as Pos from cs_threads
) as T
where T.Pos > 100000 and T.Pos < 100030

以上是原文提供的查询例子,其中最重要的就是ROW_NUMBER()了,这个东西需要结合order by来使用。:)

posted on Thursday, January 19, 2006 10:55:00 PM (China Standard Time, UTC+08:00)  #    Comments [1]
# Thursday, November 17, 2005

一朋友问及一个问题:在SQL Server2000里如何通过sql语句查出指定数据表中自动编号的字段名,即包含标识的字段名。
其实使用一个简单的sql语句就搞定了:

Select [name] From sysColumns Where id=object_id(N'TABLENAME') and Status=128

其中TABLENAME为你要查询的数据表名称。

如果想查询标识种子和标识增量的信息,应使用如下语句:

Select [name],autoval From sysColumns Where id=object_id(N'TABLENAME') and Status=128

autoval字段的值包含了标识种子和标识增量信息,具体内容相信大家一看即明白了。在此不多说了^_^

另外,有人说查询主键时用Status=128是错误的,Status=128是用来查询标识字段的。

posted on Thursday, November 17, 2005 2:42:50 PM (China Standard Time, UTC+08:00)  #    Comments [0]
# Tuesday, October 18, 2005

SqlServer的默认开放端口是1433,但是由于实际开发的需要端口被设置为非1433,一些开发人员不知如何写该类型的连接字符串了。
其实解决这个问题很简单的,只需在连接字符串的Data Source=myServer后面加上逗号和端口号就可以了。例如“……User ID=sa;Initial Catalog=pubs;Data Source=myServer,1200”,就是连接的1200端口的Sql Server服务器了。

具体的描述如下:
症状:
当您使用传输控制协议/Internet 协议 (TCP/IP) 以外的协议时,如果您指定 1433 以外的一个端口用以连接到 Microsoft SQL Server 的一个实例,则 SqlConnection.Open 会失败。
解决方案:
若要解决此问题,请使用 TCP/IP 协议,并使连接字符串中包括"Server=ComputerName, PortNumber"。

posted on Tuesday, October 18, 2005 9:12:51 PM (China Standard Time, UTC+08:00)  #    Comments [2]
# Tuesday, October 11, 2005
这是一个很好的分页存储过程的解决方案,向大家推荐。
posted on Tuesday, October 11, 2005 7:54:53 PM (China Standard Time, UTC+08:00)  #    Comments [3]
# Friday, August 19, 2005

 在数据库应用的设计中,我们往往会需要获取某些表的记录总数,用于判断表的记录总数是否过大,是否需要备份数据等。我们通常的做法是:select count(*) as c from tableA 。然而对于记录数巨大的表,上述做法将会非常耗时。在DELL 4400 服务器上做试验,MS Sqlserver 2000 数据库对于100万记录的简单数据表执行上述语句,时间在1分钟以上。如果在表的某个字段上做聚簇索引,第一次执行该语句的时间和没有索引的时间差不多,之后执行上述语句,速度很快,在1秒中以内,但当表的记录数发生较大变化后,再执行该语句又会经历一次耗时的过程。而且不是每个表都适合做聚簇索引的,对于数量巨大的表,如果需要经常增删操作,建聚簇索引是一个很不明智的做法,将会极大的影响增删的速度。那么有没有一个比较简单的方法快速获取表的记录总数呢?答案是有的。
 在MS SQL 数据库中每个表都在sysindexes 系统表中拥有至少一条记录,该记录中的rows 字段会定时记录表的记录总数。下面是sysindexes 表的相关记录的含义:

列名      数据类型             描述
id            int                        表ID(如果 indid = 0 或255)。否则为索引所属表的ID
Indid       smallint                索引ID:
                                         0=表
                                         1=聚簇索引
                                         >1=非聚簇索引
                                        255=具有text或image数据的表条目。
rows       int                       基于indid=0 和 indid=1地数据级行数,该值对于indid>1重 复。如果indid=255,rows设置为0。
  
  
当表没有聚簇索引时,Indid = 0 否则为 1。

那么现在大家应该知道如何获取表的记录总数了,只需执行如下语句:
select rows from sysindexes where id = object_id(tablename) and indid in (0,1)

该方法获取表的记录总数的速度非常快,在毫秒级就可以完成,相比select count(*) 要快上数万倍,但是大家在运用该方法是一定要主要,该方法得到的表的总记录数不是一个精确值,原因是MS SQL 并不是实时更新该字段的值,而是定时更新,当从实践来看该值和精确值一般误差不大,如果你希望快速的粗略估算表的大小,建议你采用该方法。如果你希望得到精确值,那么请在执行上述语句前执行DBCC UPDATEUSAGE(DatabaseName,[TABLENAME]) WITH ROW_COUNTS 强制更新该字段的值,但这样第一次更新时会耗费大量的时间,这样做的效果和建有聚簇索引的表 select count (*) 效果相差不大,所以如果你希望相对快速地得到精确的表的记录总数,那么你有两种选择,建聚簇索引或者先DBCC 再使用上述方法。

posted on Friday, August 19, 2005 10:56:31 AM (China Standard Time, UTC+08:00)  #    Comments [0]
# Thursday, August 18, 2005

/*
  经测试,在 14483461 条记录中查询第 100000 页,每页 10 条记录按升序和降序第一次时间均为 0.47 秒,第二次时间均为 0.43 秒,测试语法如下:
  exec GetRecordFromPage news,newsid,10,100000
  news 为 表名, newsid 为关键字段, 使用时请先对 newsid 建立索引。
*/

/*
  函数名称: GetRecordFromPage
  函数功能: 获取指定页的数据
  参数说明: @tblName      包含数据的表名
           @fldName      关键字段名
           @PageSize     每页记录数
           @PageIndex    要获取的页码
           @OrderType    排序类型, 0 - 升序, 1 - 降序
           @strWhere     查询条件 (注意: 不要加 where)
  作  者: 铁拳
  邮  箱:
sunjianhua_kki@sina.com
  创建时间: 2004-07-04
  修改时间: 2004-07-04
*/
CREATE PROCEDURE GetRecordFromPage
    @tblName      varchar(255),       
-- 表名
    @fldName      varchar(255),       -- 字段名
    @PageSize     int = 10,          
-- 页尺寸
    @PageIndex    int = 1,           
-- 页码
    @OrderType    bit = 0,           
-- 设置排序类型, 非 0 值则降序
    @strWhere     varchar(2000) = ''  
-- 查询条件 (注意: 不要加 where)
AS

declare @strSQL   varchar(6000)       -- 主语句
declare @strTmp   varchar(1000)       -- 临时变量
declare @strOrder varchar(500)        -- 排序类型

if @OrderType != 0
begin
    set @strTmp = '<(select min'
    set @strOrder = ' order by [' + @fldName + '] desc'
end
else
begin

    set @strTmp = '>(select max'
    set @strOrder = ' order by [' + @fldName +'] asc'
end

set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
    + @tblName + '] where [' + @fldName + ']' + @strTmp + '(['
    + @fldName + ']) from (select top ' + str((@PageIndex-1)*@PageSize) + ' ['
    + @fldName + '] from [' + @tblName + ']' + @strOrder + ') as tblTmp)'
    + @strOrder

if @strWhere != ''
    set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
        + @tblName + '] where [' + @fldName + ']' + @strTmp + '(['
        + @fldName + ']) from (select top ' + str((@PageIndex-1)*@PageSize) + ' ['
        + @fldName + '] from [' + @tblName + '] where ' + @strWhere + ' '
        + @strOrder + ') as tblTmp) and ' + @strWhere + ' ' + @strOrder

if @PageIndex = 1
begin
    set @strTmp = ''
    if @strWhere != ''
        set @strTmp = ' where (' + @strWhere + ')'

    set @strSQL = 'select top ' + str(@PageSize) + ' * from ['
        + @tblName + ']' + @strTmp + ' ' + @strOrder
end

exec (@strSQL)

GO

posted on Thursday, August 18, 2005 9:26:54 AM (China Standard Time, UTC+08:00)  #    Comments [3]
# Tuesday, April 05, 2005
Cω是微软研究院对下一代语言的探索。它扩展了C#的功能,更好的支持数据访问(SQL和XML)和并发控制。 Cω的希望能方便的开发出更加可靠且维护性更好的软件,其中一个重要的概念就是"尽早的发现错误"。 在下面的一个数据库查询的例子中将很好的体现出这一点。
posted on Tuesday, April 05, 2005 11:24:39 AM (China Standard Time, UTC+08:00)  #    Comments [0]
# Wednesday, March 23, 2005
一个程序详细研究DataReader,同时显示Command用法; 功能:演示DataReader的各项操作; 说明:如果需要文字说明请查看我的BLOG里面关于ado.net的文章; 作者:雪冬寒; BOLG:http://blog.csdn.net/bineon
posted on Wednesday, March 23, 2005 5:08:34 PM (China Standard Time, UTC+08:00)  #    Comments [0]
近日被一个问题给疑惑:如何向一个数据库表内插入一个数据,然后获得插入后的产生的主键值,并将该值和其他参数写入另一个表,因为一直使用mysql, access等不含存储过程的数据库,所以百死不得其解,终于从日本朋友那里得到了使用存储过程实现解决办法,其实看看也挺简单的。 都怪自己学艺不精。


程序代码:
CREATE procedure develop.insert_base_grid(@b_i int, @e_c int,@p_c varchar(192),@g_cint,@g_n int)
as
    -- 変数宣言

DECLARE @parent INT -- 親レコードのID 保存
DECLARE @error INT -- エラー値 保存
-- トランザクション開始
BEGIN TRANSACTION
    -- 親レコードの挿入

    INSERT INTO develop.syuko_base ( bills_id, employee_cd, prmss_comments,prmss_update, prmss_registdate )
    SELECT @b_i AS チラシID, @e_c AS 出稿MDCD, @p_c AS コメント, getDate()AS 更新日, getDate() AS 登録日;
    SET @error = @@ERROR
    IF @ERROR <> 0 GOTO EXIST_ERR
    -- 親レコードのID の値を保存
    SET @parent = SCOPE_IDENTITY()

    -- 子レコードの挿入
    INSERT INTO develop.gridinfo ( prmss_id, grid_cd, grid_num)
    SELECT @parent AS 出稿CD, @g_c AS グリッドCD, @g_n AS グリッド数;
    SET @error = @@ERROR
    IF @ERROR <> 0 GOTO EXIST_ERR

    -- トランザクションのコミット
    COMMIT TRANSACTION
    GOTO EXIST

-- エラー処理
EXIST_ERR:
    ROLLBACK TRANSACTION
    PRINT @error
    GOTO EXIST

-- 終了処理
EXIST:
GO
posted on Wednesday, March 23, 2005 4:34:11 PM (China Standard Time, UTC+08:00)  #    Comments [0]