आईडी या वस्तु पास करें


38

डोमेन इकाई प्राप्त करने के लिए व्यावसायिक तर्क पद्धति प्रदान करते समय, पैरामीटर को एक वस्तु या एक आईडी स्वीकार करनी चाहिए? उदाहरण के लिए, क्या हमें ऐसा करना चाहिए:

public Foo GetItem(int id) {}

या यह:

public Foo GetItem(Foo foo) {}

मैं वस्तुओं को अपनी संपूर्णता में पारित करने में विश्वास करता हूं, लेकिन इस मामले के बारे में क्या है जहां हमें एक वस्तु मिल रही है और हम केवल आईडी जानते हैं? क्या कॉलर को एक खाली Foo बनाना चाहिए और ID सेट करना चाहिए, या क्या उसे आईडी को विधि में पास करना चाहिए? चूंकि आने वाली फू खाली होगी, आईडी को छोड़कर, मुझे फोन करने वाले को फू बनाने और अपनी आईडी सेट करने का लाभ नहीं दिखता है जब वह आईडी को गेट इटेम () विधि पर भेज सकता है।

जवाबों:


42

लुकअप के लिए सिर्फ सिंगल फील्ड का इस्तेमाल किया जा रहा है।

फोन करने वाले के पास नहीं है Foo, यह एक पाने की कोशिश कर रहा है। ज़रूर, आप Fooखाली छोड़ दिए गए अन्य सभी क्षेत्रों के साथ एक अस्थायी बना सकते हैं , लेकिन यह केवल तुच्छ डेटा संरचनाओं के लिए काम करता है। अधिकांश वस्तुओं में ऐसे हमलावर होते हैं जो अधिकतर-खाली-वस्तु के दृष्टिकोण से उल्लंघन करते हैं, इसलिए इससे बचें।


धन्यवाद। मुझे उसके उत्तर में अमीरराम के # 2 अंक के साथ यह उत्तर पसंद है।
बॉब हॉर्न

3
यह तर्कसंगत लगता है। लेकिन प्रदर्शन के लिहाज से, मैं उन क्षेत्रों में दौड़ चुका हूं, जहां कॉल करने वाले की वस्तु हो सकती है और यह नहीं। केवल आईडी पास करने से उक्त वस्तु डेटाबेस से दो बार पढ़ी जा सकती है। क्या यह सिर्फ एक स्वीकार्य प्रदर्शन हिट है? या क्या आप पास होने के लिए आईडी या ऑब्जेक्ट दोनों की संभावना प्रदान करते हैं?
कॉम्प्यूट्रियस

मैं इन दिनों नमक के एक दाने के साथ 'कभी भी वस्तु को पास नहीं' करता हूं। यह सिर्फ आपके संदर्भ / परिदृश्य पर निर्भर करता है।
ब्रूनो

12

क्या यह अब या भविष्य में किसी भी समय तारांकित (क्रमबद्ध / deserialized) हो जाएगा? कौन जानता है, कैसे-बड़ी पूर्ण वस्तु पर एकल आईडी प्रकार के अनुकूल है।

यदि आप अपनी संस्था को आईडी की टाइप-सुरक्षा की तलाश कर रहे हैं, तो कोड समाधान भी हैं। अगर आपको एक उदाहरण की आवश्यकता है तो मुझे बताएं।

संपादित करें: आईडी के प्रकार-सुरक्षा पर विस्तार:

तो, चलिए आपकी विधि लेते हैं:

public Foo GetItem(int id) {}

हम केवल यह आशा करते हैं कि पूर्णांक idजो किसी Fooवस्तु के लिए पास हुआ है । कोई इसका दुरुपयोग कर सकता है और किसी Barवस्तु के पूर्णांक आईडी या यहां तक ​​कि केवल हाथ के प्रकार से भी गुजर सकता है 812341। यह सुरक्षित नहीं है Foo। दूसरे, भले ही आपने पास वस्तु Fooसंस्करण का उपयोग किया हो , मुझे यकीन है कि Fooएक आईडी फ़ील्ड है, intजिसे कोई संभवतः संशोधित कर सकता है। और अंत में, आप विधि अधिभार का उपयोग नहीं कर सकते हैं यदि ये एक वर्ग में एक साथ मौजूद होते हैं क्योंकि केवल रिटर्न प्रकार भिन्न होता है। आइए C # में टाइप-सेफ दिखने के लिए इस विधि को थोड़ा फिर से लिखें:

public Foo GetItem(IntId<Foo> id) {}

इसलिए मैंने एक क्लास शुरू की है जिसका नाम IntIdहै जेनेरिक पीस। इस विशेष मामले में, मैं चाहता हूँ intकि Fooकेवल के साथ जुड़ा हुआ है । मैं सिर्फ एक नग्न में पास नहीं कर सकता intऔर न ही मैं IntId<Bar>इसे गलती से असाइन कर सकता हूं । तो नीचे बताया गया है कि मैंने इन प्रकार के सुरक्षित पहचानकर्ताओं को कैसे लिखा है। ध्यान रखें कि वास्तविक अंतर्निहित intका हेरफेर केवल आपके डेटा एक्सेस लेयर पर है। उपरोक्त कुछ भी केवल मजबूत प्रकार को देखता है और इसकी आंतरिक intआईडी तक कोई (प्रत्यक्ष) पहुंच नहीं है । इसका कोई कारण नहीं होना चाहिए।

IModelId.cs इंटरफ़ेस:

namespace GenericIdentifiers
{
    using System.Runtime.Serialization;
    using System.ServiceModel;

    /// <summary>
    /// Defines an interface for an object's unique key in order to abstract out the underlying key
    /// generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [ServiceContract]
    public interface IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        /// <value>The origin.</value>
        [DataMember]
        string Origin
        {
            [OperationContract]get;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="IModelId{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        [OperationContract]
        TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal; otherwise
        /// <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns><c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.</returns>
        [OperationContract]
        bool Equals(IModelId<T> obj);
    }
}

ModelIdBase.cs आधार वर्ग:

namespace GenericIdentifiers
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an object's unique key in order to abstract out the underlying key generation/maintenance mechanism.
    /// </summary>
    /// <typeparam name="T">The type the key is representing.</typeparam>
    [DataContract(IsReference = true)]
    [KnownType("GetKnownTypes")]
    public abstract class ModelIdBase<T> : IModelId<T> where T : class
    {
        /// <summary>
        /// Gets a string representation of the domain the model originated from.
        /// </summary>
        [DataMember]
        public string Origin
        {
            get;

            internal set;
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public abstract TKeyDataType GetKey<TKeyDataType>();

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned. All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public abstract bool Equals(IModelId<T> obj);

        protected static IEnumerable<Type> GetKnownTypes()
        {
            return new[] { typeof(IntId<T>), typeof(GuidId<T>) };
        }
    }
}

IntId.cs:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, Integer Identifier={Id}")]
    [DataContract(IsReference = true)]
    public sealed class IntId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal int Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="intIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="intIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
        {
            return !object.Equals(intIdentifier1, intIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="IntId{T}"/> to <see cref="System.Int32"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator int(IntId<T> id)
        {
            return id == null ? int.MinValue : id.GetKey<int>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Int32"/> to <see cref="IntId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator IntId<T>(int id)
        {
            return new IntId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<int>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<int>().ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<int>() == this.GetKey<int>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            int id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return int.TryParse(originAndId[1], NumberStyles.None, CultureInfo.InvariantCulture, out id)
                ? new IntId<T> { Id = id, Origin = originAndId[0] }
                : null;
        }
    }
}

और, मेरे कोडबेस की पूर्णता के लिए, मैंने GUID संस्थाओं के लिए एक भी लिखा है, GuideId.cs:

namespace GenericIdentifiers
{
    // System namespaces
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents an abstraction of the database key for a Model Identifier.
    /// </summary>
    /// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
    [DebuggerDisplay("Origin={Origin}, GUID={Id}")]
    [DataContract(IsReference = true)]
    public sealed class GuidId<T> : ModelIdBase<T> where T : class
    {
        /// <summary>
        /// Gets or sets the unique ID.
        /// </summary>
        /// <value>The unique ID.</value>
        [DataMember]
        internal Guid Id
        {
            get;

            set;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator ==(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="guidIdentifier1">The first Model Identifier to compare.</param>
        /// <param name="guidIdentifier2">The second Model Identifier to compare.</param>
        /// <returns>
        ///   <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public static bool operator !=(GuidId<T> guidIdentifier1, GuidId<T> guidIdentifier2)
        {
            return !object.Equals(guidIdentifier1, guidIdentifier2);
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="GuidId{T}"/> to <see cref="System.Guid"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator Guid(GuidId<T> id)
        {
            return id == null ? Guid.Empty : id.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Guid"/> to <see cref="GuidId{T}"/>.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator GuidId<T>(Guid id)
        {
            return new GuidId<T> { Id = id };
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
        /// <see cref="T:System.Object"/>.</param>
        /// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
        /// <see cref="T:System.Object"/>; otherwise, false.</returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as IModelId<T>);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hash = 17;

                hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
                return (31 * hash) + this.GetKey<Guid>().GetHashCode();
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.Origin + ":" + this.GetKey<Guid>();
        }

        /// <summary>
        /// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
        /// otherwise <c>false</c> is returned.  All implementations must also override the equal operator.
        /// </summary>
        /// <param name="obj">The identifier to compare against.</param>
        /// <returns>
        ///   <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
        /// </returns>
        public override bool Equals(IModelId<T> obj)
        {
            if (obj == null)
            {
                return false;
            }

            return (obj.Origin == this.Origin) && (obj.GetKey<Guid>() == this.GetKey<Guid>());
        }

        /// <summary>
        /// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
        /// Typically, this is a database key, file name, or some other unique identifier.
        /// </summary>
        /// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
        /// <returns>The unique key as the data type specified.</returns>
        public override TKeyDataType GetKey<TKeyDataType>()
        {
            return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Generates an object from its string representation.
        /// </summary>
        /// <param name="value">The value of the model's type.</param>
        /// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
        internal static ModelIdBase<T> FromString(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            Guid id;
            var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

            if (originAndId.Length != 2)
            {
                throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
            }

            return Guid.TryParse(originAndId[1], out id) ? new GuidId<T> { Id = id, Origin = originAndId[0] } : null;
        }
    }
}

हाँ, यह तार पर जा रहा है। मुझे नहीं पता है कि मुझे इसकी इकाई के लिए आईडी के प्रकार-सुरक्षा की आवश्यकता है, लेकिन मुझे यह देखने में दिलचस्पी है कि आपके द्वारा इसका क्या मतलब है। तो हाँ, अगर आप उस पर विस्तार कर सकते हैं, तो यह अच्छा होगा।
बॉब हॉर्न

मैंने ऐसा किया है। थोड़ा कोड-भारी हो गया :)
जेसी सी। स्लाइसर

1
वैसे, मैंने Originसंपत्ति की व्याख्या नहीं की है: यह SQL सर्वर parlance में स्कीमा की तरह है। Fooआपके खाते के सॉफ़्टवेयर में इसका उपयोग हो सकता है और Fooमानव संसाधन के लिए एक और जो आपके डेटा एक्सेस लेयर पर अलग से बताने के लिए मौजूद है। या, यदि आपके पास कोई संघर्ष नहीं है, तो इसे अनदेखा करें जैसे मैं करता हूं।
जेसी सी। स्लीकर

1
@ JesseC.Slicer: पहली नज़र में, यह ओवर-इंजीनियरिंग कुछ के लिए एक आदर्श उदाहरण की तरह दिखता है।
डॉक्टर ब्राउन

2
@DocBrown कंधे उचकाने की क्रिया प्रत्येक अपने स्वयं के लिए। यह एक समाधान है जिसकी कुछ लोगों को आवश्यकता है। कुछ लोग नहीं करते। यदि YAGNI, तो इसका उपयोग न करें। यदि आपको इसकी आवश्यकता है, तो यह है।
जेसी सी। स्लीकर

5

मैं निश्चित रूप से आपके निष्कर्ष से सहमत हूं। आईडी पास करना कुछ कारणों से पसंद किया जाता है:

  1. यह आसान है। घटकों के बीच इंटरफेस सरल होना चाहिए।
  2. Fooकेवल आईडी के लिए एक ऑब्जेक्ट बनाने का मतलब है कि झूठे मूल्यों का निर्माण। कोई गलती कर सकता है और इन मूल्यों का उपयोग कर सकता है।
  3. intअधिक विस्तृत है और सभी आधुनिक भाषाओं में इसे मूल रूप से घोषित किया जा सकता है। एक बनाने के लिए Fooविधि कॉल करने वाले को वस्तु, तो आप शायद एक जटिल डेटा संरचना (json वस्तु की तरह) बनाने की आवश्यकता होगी।

4

मुझे लगता है कि आप वस्तु की पहचानकर्ता पर लुक स्थापित करने में समझदार होंगे जैसा कि बेन वोइगट ने सुझाया है।

हालाँकि, याद रखें कि आपके ऑब्जेक्ट का पहचानकर्ता का प्रकार बदल सकता है। जैसे, मैं अपने प्रत्येक आइटम के लिए एक पहचानकर्ता वर्ग बनाऊंगा, और केवल इन पहचानकर्ताओं के इन उदाहरणों के माध्यम से वस्तुओं को देखने की अनुमति दूंगा। निम्नलिखित उदाहरण देखें:

public class Item
{
  public class ItemId
  {
    public int Id { get; set;}
  }

  public ItemId Id; { get; set; }
}

public interface Service
{
  Item GetItem(ItemId id);
}

मैंने एनकैप्सुलेशन का उपयोग किया, लेकिन आप Itemइनहेरिट भी कर सकते हैं ItemId

इस तरह, यदि आपकी आईडी का प्रकार सड़क के साथ बदलता है, तो आपको Itemकक्षा में, या गेट इटेम विधि के हस्ताक्षर में कुछ भी नहीं बदलना होगा । केवल सेवा के कार्यान्वयन में आपको अपना कोड बदलना होगा (जो कि केवल एक चीज है जो सभी मामलों में बदलता है)


2

यह इस बात पर निर्भर करता है कि आपकी विधि क्या करती है।

आम तौर पर Get methods, id parameterवस्तु को वापस लेना और प्राप्त करना सामान्य ज्ञान है । अपडेट के लिए या SET methodsआपने पूरी वस्तु को सेट / अपडेट करने के लिए भेजा होगा।

कुछ अन्य मामलों में जहां आपके method is passing search parameters(व्यक्तिगत आदिम प्रकारों के संग्रह के रूप में) परिणामों का एक सेट प्राप्त करने के लिए, यह use a container to holdआपके खोज मापदंडों के लिए बुद्धिमान हो सकता है । यह उपयोगी है यदि लंबे समय में मापदंडों की संख्या बदल जाएगी। इस प्रकार, आपको would not needबदलना होगा signature of your method, add or remove parameter in all over the places

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.