为什么无法通过字典键和对象引用设置.NET对象属性值?

时间:2019-04-10 23:24:11

标签: c# .net dictionary properties .net-core

我已经创建了一个数据类,我打算将其用于发送要保留在数据库中的数据,并以强类型的方式从数据库返回数据。除了其属性外,该类还包含一个Dictionary,我在构造函数中填充了每个属性的名称和引用。这使属性可枚举,并使我能够使用“ foreach”遍历它们。

当设置属性值并发送要持久存储在数据库中的对象时,这非常有用。我可以遍历Dictionary键,获取每个属性的值,并使用键作为参数名称和属性值作为参数值为每个属性添加SqlParameter。

但是,以另一种方式行不通。我可以遍历Dictionary键并获取SqlDataReader每行中每一列的值,但是当我尝试使用Dictionary对相应对象属性的引用将这些值分配给我的数据对象时,会发生奇怪的事情。分配成功,但数据对象属性全部保留其初始默认值。我可以查看数据对象属性并查看这些初始的默认值。我还可以查看Dictionary条目值,并查看从SqlDataReader读取和分配的更新值。

这没有任何意义。该词典应该通过其键(“字符串”泛型类型)提供对每个属性(“对象”泛型类型)的访问,但其作用类似于维护每个词典“ KeyValuePair”的单独副本。

有什么作用?

我正在C#中在macOS 10.13.6 High Sierra上运行的ASP.NET Core 2.1.1项目的上下文中完成所有这些工作。

我已经对StackOverflow进行了广泛的搜索,并且我发现了很多有关使用反射来完成这种事情的建议。我将在必要时重构代码以使用反射,但是我真的很想了解我正在发生的事情的思维模型在哪里以及如何关闭。

对正在发生的事情以及为什么要得到其赞赏的解释。

带有属性字典的示例数据类

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;

namespace MyOrg.MyProj.Data
{
    [DataContract]
    public class DataObj
    {
        #region Attributes

        [Required]
        [DataMember(Name = "dataObjectId")]
        public Int64 DataObjectId { get; set; }

        [Required]
        [DataMember(Name = "guid")]
        public Guid Guid { get; set; }

        public virtual Dictionary<string, object> DataMembers { get; set; }     //NOTE: Implements the IEnumerable interface in order to support 'foreach' operations, etc on 'DataObj' class attributes

        #endregion Attributes

        #region Constructors

        public DataObj(Int64 dataObjectId, Guid guid)
        {
            try
            {
                DataObjectId = dataObjectId;
                Guid = guid;

                DataMembers = new Dictionary<string, object>
                {
                    { "DataObjectId", DataObjectId },
                    { "Guid", Guid }
                };
            }
            catch (Exception e)
            {
                Console.WriteLine($"RUNTIME EXCEPTION while INSTANTIATEing DataObj, " + e.Message + ", " + e.StackTrace);
            }
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Implements the IEnumerable interface in order to support 'foreach' operations, etc on 'DataObj' class attributes
        /// </summary>
        /// <returns>Enumerator</returns>
        public Dictionary<string, object>.Enumerator Enumerator()
        {
            return DataMembers.GetEnumerator();     //NOTE: Return the Dictionary object's IEnumerator rather than implementing IEnumerable for the 'DataObj' class itself
        }

        #endregion Methods

数据访问类示例(节选)

                reader = command.ExecuteReader();

                dataObjList = new List<DataObj>();

                if (reader.HasRows)
                {
                    while (reader.Read())
                    {
                        tempDataObj = new DataObj(-1, new Guid("00000000-0000-0000-0000-000000000000"));

                        keys = new List<String>(tempDataObj.DataMembers.Keys);       //NOTE: Can't modify a Dictionary while iterating through it.  See the 'Why This Error?' section of https://stackoverflow.com/questions/604831/collection-was-modified-enumeration-operation-may-not-execute

                        foreach (String key in keys)
                        {
                            tempDataObj.DataMembers[key] = reader[key];
                        }

                        dataObjList.Add(tempDataObj);

对于'key'='DataObjectId','Guid'等,我希望tempDataObj.DataObjectId,tempDataObj.Guid等的值设置为'reader [key]'中从数据库返回的值。

相反,它保留在构造函数中设置的初始默认值,即“ -1”。对于和引用数据类型都是如此。

但是,当我检查tempDataObj.DataMembers [“ DataObjectId”]时,已将其设置为'reader [key]'中从数据库返回的值。

Inspecting the Object Property and Dictionary Values

tempDataObj.DataMembers [“ DataObjectId”]应该引用tempDataObj.DataObjectId属性,等等,但是Dictionary似乎在维护自己的值,而不是提供对“ DataObjectId”属性的对象引用。

这是怎么回事?谢谢!

2 个答案:

答案 0 :(得分:0)

我看到两条(主要)路线可以做您想要的。在这两种情况下,您都应该实现自定义索引器。

  1. 在索引器中显式检查为其指定的名称,并相应地获取或设置字段或属性。

  2. 使用反射,即GetField()GetProperty()获取字段或属性,并使用GetValue()SetValue()获取或设置值。

下面是一个演示,其中ExposeByExplicitIndexer0及其子孙使用方式1,ExposeByIndexerUsingReflection0及其子孙使用方式2。

public class ExposeByExplicitIndexer0
{
    public int Int0 = 1;
    public string String0 = "A";

    public virtual object this[string name]
    {
        get
        {
            switch (name)
            {
                case "Int0":
                    return this.Int0;
                case "String0":
                    return this.String0;
                default:
                    throw new IndexOutOfRangeException();
            }
        }
        set
        {
            switch (name)
            {
                case "Int0":
                    this.Int0 = (int)value;
                    break;
                case "String0":
                    this.String0 = (string)value;
                    break;
                default:
                    throw new IndexOutOfRangeException();
            }
        }
    }
}

public class ExposeByExplicitIndexer1 : ExposeByExplicitIndexer0
{
    protected Guid _Guid1 = Guid.Empty;
    public Guid Guid1
    {
        get
        {
            return this._Guid1;
        }
        set
        {
            this._Guid1 = value;
        }
    }

    public override object this[string name]
    {
        get
        {
            switch (name)
            {
                case "Guid1":
                    return this.Guid1;
                default:
                    return base[name];
            }
        }
        set
        {
            switch (name)
            {
                case "Guid1":
                    this.Guid1 = (Guid)value;
                    break;
                default:
                    base[name] = value;
                    break;
            }
        }
    }
}

public class ExposeByIndexerUsingReflection0
{
    public object this[string name]
    {
        get
        {
            FieldInfo fieldInfo;
            if ((fieldInfo = this.GetType().GetField(name)) != null)
            {
                return fieldInfo.GetValue(this);
            }
            PropertyInfo propertyInfo;
            if ((propertyInfo = this.GetType().GetProperty(name)) != null)
            {
                return propertyInfo.GetValue(this);
            }
            throw new IndexOutOfRangeException();
        }
        set
        {
            FieldInfo fieldInfo;
            if ((fieldInfo = this.GetType().GetField(name)) != null)
            {
                fieldInfo.SetValue(this, value);
                return;
            }
            PropertyInfo propertyInfo;
            if ((propertyInfo = this.GetType().GetProperty(name)) != null)
            {
                propertyInfo.SetValue(this, value);
                return;
            }
            throw new IndexOutOfRangeException();
        }
    }
}

public class ExposeByIndexerUsingReflection1 : ExposeByIndexerUsingReflection0
{
    public int Int1 = 1;
    public string String1 = "A";
}

public class ExposeByIndexerUsingReflection2 : ExposeByIndexerUsingReflection1
{
    protected Guid _Guid2 = Guid.Empty;
    public Guid Guid2
    {
        get
        {
            return this._Guid2;
        }
        set
        {
            this._Guid2 = value;
        }
    }
}

public class Program
{
    static void Main(string[] args)
    {
        Guid newGuid = Guid.NewGuid();

        Console.WriteLine("Expose by explicit indexer:");
        ExposeByExplicitIndexer1 exposeByExplicitIndexer1 = new ExposeByExplicitIndexer1();
        exposeByExplicitIndexer1["Int0"] = 10;
        exposeByExplicitIndexer1["String0"] = "AAA";
        exposeByExplicitIndexer1["Guid1"] = newGuid;
        Console.WriteLine("output via indexer:");
        Console.WriteLine(exposeByExplicitIndexer1["Int0"]);
        Console.WriteLine(exposeByExplicitIndexer1["String0"]);
        Console.WriteLine(exposeByExplicitIndexer1["Guid1"]);
        Console.WriteLine("output via fields or properties:");
        Console.WriteLine(exposeByExplicitIndexer1.Int0);
        Console.WriteLine(exposeByExplicitIndexer1.String0);
        Console.WriteLine(exposeByExplicitIndexer1.Guid1);

        Console.WriteLine("Expose by indexer using reflection:");
        ExposeByIndexerUsingReflection2 exposeByIndexerUsingReflection2 = new ExposeByIndexerUsingReflection2();
        exposeByIndexerUsingReflection2["Int1"] = 10;
        exposeByIndexerUsingReflection2["String1"] = "AAA";
        exposeByIndexerUsingReflection2["Guid2"] = newGuid;
        Console.WriteLine("output via indexer:");
        Console.WriteLine(exposeByIndexerUsingReflection2["Int1"]);
        Console.WriteLine(exposeByIndexerUsingReflection2["String1"]);
        Console.WriteLine(exposeByIndexerUsingReflection2["Guid2"]);
        Console.WriteLine("output via fields or properties:");
        Console.WriteLine(exposeByIndexerUsingReflection2.Int1);
        Console.WriteLine(exposeByIndexerUsingReflection2.String1);
        Console.WriteLine(exposeByIndexerUsingReflection2.Guid2);

        Console.Read();
    }
}

以方式1,每个添加新字段或属性的子代都必须扩展索引器。一般来说,这是更多的工作,但也提供了一种简便的灵活性方法,即用于添加一些强制类型转换或通过别名公开某些字段或属性等。

方法2在后代中所需的精力更少。但是,与方式1一样灵活,反过来可能会变得更加困难。也许某些混合解决方案也可能会覆盖某些后代中的索引器以注入特殊逻辑。

答案 1 :(得分:0)

您要存储两次数据-一次在Dictionary中存储一次,第二次在字段中存储。无需将其存储两次。只需这样做:

[DataContract]
public class DataObj
{
    [Required]
    [DataMember(Name = "dataObjectId")]
    public Int64 DataObjectId
    {
        get => (long)DataMembers[nameof(DataObjectId)];
        set => DataMembers[nameof(DataObjectId)] = value;
    }

    [Required]
    [DataMember(Name = "guid")]
    public Guid Guid
    {
        get => (Guid)DataMembers[nameof(Guid)];
        set => DataMembers[nameof(Guid)] = value;
    }

    public Dictionary<string, object> DataMembers { get; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

    public DataObj(Int64 dataObjectId, Guid guid)
    {
        DataObjectId = dataObjectId;
        Guid = guid;
    }

    public Dictionary<string, object>.Enumerator Enumerator()
    {
        return DataMembers.GetEnumerator();
    }
}

仅供参考,您还可以使用ExpandoObject进行查看,它使您可以以类似于类的方式访问某些内容,但实际上只是一个Dictionary。 https://docs.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.7.2

我从没使用过ExpandoObject,我认为整个想法与VBA的默认设置option explicit关闭和On Error Resume Next一样不正确。另一方面,我不怎么处理数据库。

相关问题