Почему нельзя установить значения свойств объекта .NET с помощью словарных ключей и ссылок на объекты? - PullRequest
0 голосов
/ 11 апреля 2019

Я создал класс данных, который планирую использовать для отправки данных, которые будут сохранены в базе данных, и для возврата данных из базы данных строго типизированным способом. В дополнение к своим свойствам класс содержит словарь, который я заполняю в конструкторе именем и ссылкой на каждое свойство. Это делает свойства перечисляемыми и позволяет мне перебирать их, используя 'foreach'.

Это прекрасно работает при установке значений свойств и отправке объекта в базу данных. Я могу перебирать ключи словаря, получать значение каждого свойства и добавлять SqlParameter для каждого свойства, используя ключ в качестве имени параметра и значение свойства в качестве значения параметра.

Однако, идти другим путем не работает. Я могу перебирать ключи словаря и получать значение каждого столбца в каждой строке SqlDataReader, но когда я пытаюсь присвоить эти значения моему объекту данных, используя ссылку словаря на соответствующее свойство объекта, возникает любопытная вещь. Назначения выполняются успешно, НО все свойства объекта данных сохраняют свои начальные значения по умолчанию. Я могу просмотреть свойства объекта данных и увидеть эти начальные значения по умолчанию. Я также могу просматривать значения записей словаря и видеть обновленные значения, которые были прочитаны и назначены из SqlDataReader.

Это не имеет смысла. Предполагается, что Словарь обеспечивает доступ к каждому свойству (универсальному типу 'object') через его ключ (универсальный тип 'string'), но действует так, как будто он поддерживает отдельную копию каждого словаря 'KeyValuePair'.

Что дает?

Я делаю все это на C # в контексте проекта ASP.NET Core 2.1.1, работающего на macOS 10.13.6 High Sierra.

Я интенсивно искал в 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 /523774/kollektsiya-byla-izmenena-operatsiya-perechisleniya-mozhet-ne-vypolnyatsya

                        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]'.

Проверка свойств объекта и значений словаря

tempDataObj.DataMembers ["DataObjectId"] должен ссылаться на свойство tempDataObj.DataObjectId и т. Д., Но словарь, кажется, поддерживает свое собственное значение, а не предоставляет ссылку на объект на свойство DataObjectId.

Что здесь происходит? Спасибо!

Ответы [ 2 ]

0 голосов
/ 12 апреля 2019

Вы сохраняете данные дважды - один раз в словаре и второй раз в поле. Там нет необходимости хранить его дважды. Просто сделай это:

[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, который позволяет вам получить доступ к чему-то, что выглядит как класс, но на самом деле это просто словарь. https://docs.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.7.2

Я никогда не использовал ExpandoObject, и я думаю, что вся идея столь же извращена, как и то, что VBA по умолчанию выключила option explicit и On Error Resume Next. С другой стороны, я не очень разбираюсь с базами данных.

0 голосов
/ 11 апреля 2019

Я вижу два (основных) маршрута, чтобы делать то, что вы хотите.В обоих случаях вы должны реализовать пользовательский индексатор.

  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, может стать более сложным в свою очередь.Возможно, какое-то смешанное решение также возможно, переопределяя индексатор в некотором потомке, чтобы добавить специальную логику.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...