天天看點

當 ADO.NET 遇上 dynamic

傳說中的dynamic

dynamic是個不合群、不按規則辦事的家夥,可以說是個異形,但更恐怖的是它又是無所不知的,任何事情都難不了它(咳咳,它似乎與Lambda表達式是死對頭)。這令人想起《死亡日記》的怪異偵探L,行為怪異而智力超人,以至于離奇的案件不得不交給了他。dynamic可以看成是一切類型的化身,但并不是僅限于此,它像《未來戰士》續集裡面的T-1000型液體金屬的終結者。噢~~~~似乎扯的有點遠了

當 ADO.NET 遇上 dynamic

飽經風雨而不倒的ADO.NET

ADO.NET 從來做事都有理有據,而且又異常專注于自身領域,是個professional的牛人,令人想起《美麗心靈》裡面的博弈論和微分幾何學領域潛心研究以緻獲得諾貝爾經濟學獎的數學家—— 約翰·福布斯·納什 教授(咳咳,納什教授是個妄想型精神分裂的~~~嗯,這個以後再說)。

關于ADO.NET 的例子

1. 執行SQL語句

using (DbCommand command = connection.CreateCommand())
{
    command.CommandText = "select Top 10 * from Orders";
    command.CommandType = CommandType.Text;
     
    using (IDataReader reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            Console.WriteLine("OrderID: {0}, OrderDate: {1}",
                reader.GetInt32(reader.GetOrdinal("OrderID")),
                reader.GetDateTime(reader.GetOrdinal("OrderDate")));
        }
    }
}
      

2. 調用存儲過程

command.CommandText = "CustOrdersOrders";
         command.CommandType = CommandType.StoredProcedure;
         command.Parameters.Add(new SqlParameter("CustomerID", "ALFKI"));
         //略去...
      

當 ADO.NET 遇上 dynamic

某年某月ADO.NET不幸遇到dynamic,從此循規蹈矩的生活不複存在。dynamic說它可以幫助 ADO.NET 丢掉 DataSet 的包袱,而且在不用建立資料實體的情況下,實作查詢結果垮不同方法傳遞;更加強大的地方是可以與存儲過程無縫連接配接,即像調用一般方法一樣調用存儲過程而不用寫額外代碼。我的神哪~~~ ADO.NET 聽了dynamic一番遊說後,心底下不禁驚訝一下。dynamic又說,實作剛才所說的工程隻要借你手下的兩大猛将 SqlConnection 和 SqlCommand 助我一臂之力即可。

dynamic真有如此奇技? ADO.NET 雖有懷疑,但它想到曾經看過一部叫《阿甘正傳》的電影,裡面的阿甘雖然是弱智人,但參軍時練就乒乓奇技,後來還和中國國手同台競技。想到這,ADO.NET 認為不能因為對方弱智就不相信對方的話,這是很不禮貌很不紳士的人才會做的事,是以它相信了dynamic。

dynamic 果真不負衆望,三兩腳貓功夫就交出成果了。

dynamic重構後的資料庫操作
using (dynamic command = connection.CreateDynamicCommand())
{
    //執行查詢SQL
    IEnumerable<dynamic> toptenOrders = command("select Top 10 * from Orders");
    foreach (dynamic order in toptenOrders)
    {
        Console.WriteLine("OrderID: {0}, OrderDate: {1}", order.OrderID, order.OrderDate);
    }

    //執行帶參數的SQL
    IEnumerable<dynamic> customerOrders = command("select * from Orders where CustomerID = @CustomerID",
            CustomerID: "ALFKI");
    foreach (dynamic order in customerOrders)
    {
        Console.WriteLine("OrderID: {0}, OrderDate: {1}", order.OrderID, order.OrderDate);
    }

    //調用存儲過程
    IEnumerable<dynamic> orders = command.CustOrdersOrders(CustomerID: "ALFKI");
    foreach (dynamic order in orders)
    {
        Console.WriteLine("OrderID: {0}, OrderDate: {1}", order.OrderID, order.OrderDate);
    }
}
      

要知道 ADO.NET 可不是.NET菜鳥,它看到 command("select Top 10 * from Orders"); 第一感覺認為吃了dynamic藥的command有可能是委托類型,而看到後面的 command.CustOrdersOrders(CustomerID: "ALFKI"); 不得不否決了前面的看法。dynamic到底是什麼東西?可以這樣認為,dynamic什麼東西都是;也可以認為,dynamic不是什麼東西!

ADO.NET 知道任何.NET寫的再高深的代碼在reflector下都會現出原形,通過對 command 解剖,立刻明白原來自己跟《美麗心靈》的納什教授一樣糾纏于一種不存在的幻想不能自拔,reflector告訴我們:dynamic實際上是不存在的!

還是魯迅叔叔說的好,世界上本沒有dynamic,隻是微軟對委托封裝得太牛了,也便有了dynamic。

結語

聰明的你知道command是怎麼實作了嗎?不妨先想想,然後展開下面的代碼看看是否與你想的一緻。

點此展開代碼

注:本文存儲過程部分參考了微型ORM.

public static class Extensions
{
    public static DynamicCommand CreateDynamicCommand(this DbConnection connection)
    {
        return new DynamicCommand(connection);
    }
}
      
/// <summary>
    /// 動态Command
    /// </summary>
    public class DynamicCommand : DynamicObject, IDisposable
    {
        public DbConnection Connection { get; set; }

        public DynamicCommand(DbConnection connection)
        {
            this.Connection = connection;
        }

        //實作SQL語句查詢
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
            if (args.Length == 0) throw new ArgumentException("args must has value");

            result = Execute(args[0].ToString(), CommandType.Text, binder.CallInfo.ArgumentNames, args.Skip(1).ToArray());

            return true;
        }

        //實作存儲過程
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (binder.CallInfo.ArgumentNames.Count != binder.CallInfo.ArgumentCount)
            {
                throw new ArgumentException("All parameters must be named");
            }
            
            result = Execute(binder.Name, CommandType.StoredProcedure, binder.CallInfo.ArgumentNames, args);

            return true;
        }

        /// <summary>
        /// 執行SQL查詢
        /// </summary>
        /// <param name="commandText"></param>
        /// <param name="commandType"></param>
        /// <param name="names"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private object Execute(string commandText, CommandType commandType, IEnumerable<string> names, object[] args)
        {
            bool manageConnectionLifespan = (this.Connection.State == ConnectionState.Closed);

            if (manageConnectionLifespan) this.Connection.Open();

            try
            {
                using (var cmd = this.Connection.CreateCommand())
                {
                    cmd.CommandType = commandType;
                    cmd.CommandText = commandText;
                    for (int i = 0; i < args.Length; i++)
                    {
                        DbParameter param = cmd.CreateParameter();
                        param.ParameterName = "@" + names.ElementAt(i);
                        param.Value = args[i] == null ? DBNull.Value : args[i];
                        cmd.Parameters.Add(param);
                    }

                    return ExecuteList(cmd);
                }
            }
            finally
            {
                if (manageConnectionLifespan)
                {
                    this.Connection.Close();
                }
            }
        }

        /// <summary>
        /// 執行SQL指令,傳回查詢結果清單
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        private static IEnumerable<dynamic> ExecuteList(DbCommand command)
        {
            List<DynamicEntity> resultList = new List<DynamicEntity>();
            using (DbDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    DynamicEntity entity = new DynamicEntity();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        entity.SetMember(reader.GetName(i), reader.GetValue(i));
                    }
                    resultList.Add(entity);
                }
            }
            return resultList;
        }

    }
      
/// <summary>
/// 動态實體
/// </summary>
internal class DynamicEntity : DynamicObject
{
    /// <summary>
    /// 屬性和值的字典表
    /// </summary>
    private Dictionary<string, object> values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (values.ContainsKey(binder.Name))
        {
            result = values[binder.Name];
        }
        else
        {
            throw new System.MissingMemberException("The property " + binder.Name + " does not exist");
        }

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        SetMember(binder.Name, value);
        return true;
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return values.Keys;
    }

    internal void SetMember(string propertyName, object value)
    {
        if (object.ReferenceEquals(value, DBNull.Value))
        {
            values[propertyName] = null;
        }
        else
        {
            values[propertyName] = value;
        }
    }
}