डेटाबेस में एनम को बचाने के तरीके


123

किसी डेटाबेस में एनम को बचाने का सबसे अच्छा तरीका क्या है?

मुझे पता है जावा प्रदान करता है name() और valueOf()enum मानों को स्ट्रिंग और बैक में परिवर्तित करने के तरीके। लेकिन क्या इन मूल्यों को संग्रहीत करने के लिए कोई अन्य (लचीला) विकल्प हैं?

क्या अद्वितीय संख्याओं में एनम बनाने का एक स्मार्ट तरीका है (ordinal() उपयोग करने के लिए सुरक्षित नहीं है)?

अपडेट करें:

सभी भयानक और तेजी से जवाब के लिए धन्यवाद! जैसा कि मुझे संदेह था।

हालांकि 'टूलकिट' के लिए एक नोट; यह एक तरीका है। समस्या यह है कि मुझे अपने द्वारा बनाए गए प्रत्येक Enum प्रकार में समान तरीके जोड़ने होंगे। बहुत सारे डुप्लिकेट कोड और, फिलहाल, जावा इसके लिए किसी भी समाधान का समर्थन नहीं करता है (एक जावा एनम अन्य वर्गों का विस्तार नहीं कर सकता है)।


2
ऑर्डिनल () उपयोग करने के लिए सुरक्षित क्यों नहीं है?
माइकल मायर्स

कैसा डेटाबेस? MySQL में एक एनुम प्रकार है, लेकिन मुझे नहीं लगता कि यह मानक ANSI SQL है।
शरम पेंडले

6
क्योंकि किसी भी ज्ञानवर्धक जोड़ को अंत में रखा जाना चाहिए। एक
बेबस

1
समझा। यह एक अच्छी बात है कि मैं बहुत डेटाबेस के साथ सौदा नहीं है, क्योंकि मैं शायद उस के बारे में सोचा नहीं था जब तक यह बहुत देर हो चुकी है।
माइकल मायर्स

जवाबों:


165

अब हम गणना संबंधी संख्यात्मक मानों के रूप में गणना को कभी संग्रहीत नहीं करते हैं; यह डिबगिंग और समर्थन के रास्ते को बहुत कठिन बना देता है। हम स्ट्रिंग में परिवर्तित वास्तविक गणना मूल्य को संग्रहीत करते हैं:

public enum Suit { Spade, Heart, Diamond, Club }

Suit theSuit = Suit.Heart;

szQuery = "INSERT INTO Customers (Name, Suit) " +
          "VALUES ('Ian Boyd', %s)".format(theSuit.name());

और उसके बाद वापस पढ़ें:

Suit theSuit = Suit.valueOf(reader["Suit"]);

समस्या पिछले उद्यम प्रबंधक को घूरने और समझने की कोशिश में थी:

Name                Suit
==================  ==========
Shelby Jackson      2
Ian Boyd            1

छंद

Name                Suit
==================  ==========
Shelby Jackson      Diamond
Ian Boyd            Heart

बाद बहुत आसान है। पूर्व को स्रोत कोड में प्राप्त करना और संख्यात्मक मानों को खोजना आवश्यक है जो कि गणन सदस्यों को सौंपा गया था।

हाँ यह अधिक जगह लेता है, लेकिन गणना सदस्य नाम कम हैं, और हार्ड ड्राइव सस्ते हैं, और समस्या होने पर मदद करने के लिए यह अधिक योग्य है।

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

public enum Suit { Unknown, Heart, Club, Diamond, Spade }

बनना होगा:

public enum Suit { 
      Unknown = 4,
      Heart = 1,
      Club = 3,
      Diamond = 2,
      Spade = 0 }

डेटाबेस में संग्रहीत विरासत संख्यात्मक मूल्यों को बनाए रखने के लिए।

डेटाबेस में उन्हें कैसे छाँटा जाए

सवाल उठता है: मानों मैं ऑर्डर देना चाहता था। कुछ लोग एनुम के क्रमिक मूल्य द्वारा उन्हें क्रमबद्ध करना चाह सकते हैं। बेशक, गणना के संख्यात्मक मूल्य के आधार पर कार्डों का आदेश देना व्यर्थ है:

SELECT Suit FROM Cards
ORDER BY SuitID; --where SuitID is integer value(4,1,3,2,0)

Suit
------
Spade
Heart
Diamond
Club
Unknown

यह वह क्रम नहीं है जो हम चाहते हैं - हम उन्हें गणना क्रम में चाहते हैं:

SELECT Suit FROM Cards
ORDER BY CASE SuitID OF
    WHEN 4 THEN 0 --Unknown first
    WHEN 1 THEN 1 --Heart
    WHEN 3 THEN 2 --Club
    WHEN 2 THEN 3 --Diamond
    WHEN 0 THEN 4 --Spade
    ELSE 999 END

यदि आप तार को सहेजते हैं तो पूर्णांक मान सहेजने के लिए आवश्यक समान कार्य आवश्यक है:

SELECT Suit FROM Cards
ORDER BY Suit; --where Suit is an enum name

Suit
-------
Club
Diamond
Heart
Spade
Unknown

लेकिन यह वह क्रम नहीं है जो हम चाहते हैं - हम उन्हें गणना क्रम में चाहते हैं:

SELECT Suit FROM Cards
ORDER BY CASE Suit OF
    WHEN 'Unknown' THEN 0
    WHEN 'Heart'   THEN 1
    WHEN 'Club'    THEN 2
    WHEN 'Diamond' THEN 3
    WHEN 'Space'   THEN 4
    ELSE 999 END

मेरी राय है कि इस तरह की रैंकिंग उपयोगकर्ता इंटरफ़ेस में है। यदि आप उनकी गणना मूल्य के आधार पर आइटम सॉर्ट कर रहे हैं: आप कुछ गलत कर रहे हैं।

लेकिन अगर आप वास्तव में ऐसा करना चाहते हैं, तो मैं एक Suitsआयाम तालिका बनाऊंगा:

| Suit       | SuitID       | Rank          | Color  |
|------------|--------------|---------------|--------|
| Unknown    | 4            | 0             | NULL   |
| Heart      | 1            | 1             | Red    |
| Club       | 3            | 2             | Black  |
| Diamond    | 2            | 3             | Red    |
| Spade      | 0            | 4             | Black  |

इस तरह, जब आप का उपयोग करने के लिए अपने कार्ड को बदलना चाहते हैं Kissing किंग्स न्यू डेक आदेश आप इसे प्रदर्शन प्रयोजनों के लिए दूर सब अपने डेटा फेंकने के बिना परिवर्तन कर सकते हैं:

| Suit       | SuitID       | Rank          | Color  | CardOrder |
|------------|--------------|---------------|--------|-----------|
| Unknown    | 4            | 0             | NULL   | NULL      |
| Spade      | 0            | 1             | Black  | 1         |
| Diamond    | 2            | 2             | Red    | 1         |
| Club       | 3            | 3             | Black  | -1        |
| Heart      | 1            | 4             | Red    | -1        |

अब हम उपयोगकर्ताओं के लिए एक प्रदर्शन सेटिंग के साथ एक आंतरिक प्रोग्रामिंग विवरण (गणना नाम, गणना मूल्य) को अलग कर रहे हैं:

SELECT Cards.Suit 
FROM Cards
   INNER JOIN Suits ON Cards.Suit = Suits.Suit
ORDER BY Suits.Rank, 
   Card.Rank*Suits.CardOrder

23
प्रदर्शन मूल्य प्रदान करने के लिए अक्सर स्ट्रिंग को ओवरराइड किया जाता है। नाम () एक बेहतर विकल्प है क्योंकि यह
मान

9
मैं इससे दृढ़ता से असहमत हूं, अगर एनम दृढ़ता की आवश्यकता है तो नामों को जारी नहीं रखना चाहिए। जहां तक ​​इसे वापस पढ़ने के बाद यह नाम के बजाय मूल्य के साथ और भी सरल है, बस इसे टाइप कर सकते हैं जैसे कि SomeEnum enum1 = (SomeEnum) 2;
मामू

3
मामू: जब संख्यात्मक समतुल्य बदलते हैं तो क्या होता है?
इयान बॉयड

2
मैं इस दृष्टिकोण का उपयोग करके किसी को भी हतोत्साहित करूंगा। स्ट्रिंग प्रतिनिधित्व के लिए खुद को बांधना कोड लचीलापन और रीफैक्टरिंग को सीमित करता है। आपको अनूठे आईडी का बेहतर उपयोग करना चाहिए। इसके अलावा भंडारण के कचरे के भंडारण के लिए जगह खाली होती है।
ताउतविदेस

2
@LuisGouveia मैं आपसे सहमत हूं कि समय दोगुना हो सकता है। 12.37 msइसके बजाए एक क्वेरी लेना 12.3702 ms"शोर में" से मेरा यही मतलब है । आप क्वेरी को फिर से चलाते हैं और इसे लेता है 13.29 ms, या 11.36 ms। दूसरे शब्दों में, थ्रेड शेड्यूलर की यादृच्छिकता आपके द्वारा सैद्धांतिक रूप से किए गए किसी भी माइक्रो ऑप्टिमाइज़ेशन को काफी हद तक स्वाहा कर देगी, जो किसी भी तरह से किसी को भी दिखाई नहीं देता है।
इयान बॉड

42

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

सूट तालिका:

suit_id suit_name
1       Clubs
2       Hearts
3       Spades
4       Diamonds

खिलाड़ियों की मेज

player_name suit_id
Ian Boyd           4
Shelby Lake        2
  1. यदि आप कभी भी व्यवहार के साथ वर्गों (जैसे प्राथमिकता) के लिए अपनी गणना को रिफ्लेक्टर करते हैं, तो आपका डेटाबेस पहले से ही इसे सही ढंग से मॉडल करता है
  2. आपका डीबीए खुश है क्योंकि आपका स्कीमा सामान्यीकृत है (प्रति पूर्णांक के बजाय एक पूर्णांक प्रति खिलाड़ी को संग्रहीत करना, जिसमें टाइपो हो सकता है या नहीं)।
  3. आपके डेटाबेस मान ( suit_id) आपके गणना मूल्य से स्वतंत्र हैं, जो आपको अन्य भाषाओं के डेटा पर भी काम करने में मदद करता है।

14
जबकि मैं मानता हूं कि इसे सामान्यीकृत करना अच्छा है, और DB में विवश है, यह दो स्थानों में एक नया मूल्य (कोड और डीबी) जोड़ने के लिए अद्यतन का कारण बनता है, जो अधिक उपरि का कारण हो सकता है। इसके अलावा, वर्तनी की गलतियाँ किसी भी तरह की नहीं होनी चाहिए, यदि सभी अपडेट एनम नाम से प्रोग्राम किए जाते हैं।
जेसन

3
मैं ऊपर की टिप्पणी से सहमत हूं। डेटाबेस स्तर पर एक वैकल्पिक प्रवर्तन तंत्र एक बाधा ट्रिगर लिखना होगा, जो आवेषण या अपडेट को अस्वीकार कर देगा जो एक अमान्य मूल्य का उपयोग करने का प्रयास करता है।
स्टीव पर्किन्स

1
मैं एक ही सूचना को दो स्थानों पर क्यों घोषित करना चाहूंगा? CODE public enum foo {bar}और CREATE TABLE foo (name varchar);जो दोनों आसानी से सिंक से बाहर निकल सकते हैं।
इब्रोब

यदि हम अंकित मूल्य पर स्वीकृत उत्तर लेते हैं, तो यह है कि enum नाम केवल मैनुअल जांच के लिए उपयोग किए जाते हैं, तो यह उत्तर वास्तव में सबसे अच्छा विकल्प है। इसके अलावा, यदि आप गणना आदेश या मूल्यों या नामों को बदलते हैं, तो आपको हमेशा इस अतिरिक्त तालिका को बनाए रखने की तुलना में बहुत अधिक समस्याएं होंगी। विशेष रूप से जब आपको केवल डिबगिंग और समर्थन के लिए इसकी आवश्यकता होती है (और केवल अस्थायी रूप से बनाने के लिए चुन सकते हैं)।
afk5min

5

मैं तर्क दूंगा कि यहां एकमात्र सुरक्षित तंत्र स्ट्रिंग name()मूल्य का उपयोग करना है । डीबी को लिखते समय, आप मूल्य डालने के लिए एक स्प्रो का उपयोग कर सकते हैं और पढ़ते समय, व्यू का उपयोग कर सकते हैं। इस तरीके से, यदि एनम बदलते हैं, तो डीबी पर "थोप" किए बिना डेटा को प्रस्तुत करने में सक्षम होने के लिए स्प्रो / व्यू में अप्रत्यक्ष स्तर होता है।


1
मैं आपके समाधान के हाइब्रिड दृष्टिकोण और @ इयान बॉयड के समाधान का बड़ी सफलता के साथ उपयोग कर रहा हूं। पारितोषिक के लिए धन्यवाद!
मई को टेक्नोलाॅजिकल जूल

5

जैसा कि आप कहते हैं, अध्यादेश थोड़ा जोखिम भरा है। उदाहरण के लिए विचार करें:

public enum Boolean {
    TRUE, FALSE
}

public class BooleanTest {
    @Test
    public void testEnum() {
        assertEquals(0, Boolean.TRUE.ordinal());
        assertEquals(1, Boolean.FALSE.ordinal());
    }
}

यदि आपने इसे अध्यादेशों के रूप में संग्रहीत किया है, तो आपके पास पंक्तियाँ हो सकती हैं जैसे:

> SELECT STATEMENT, TRUTH FROM CALL_MY_BLUFF

"Alice is a boy"      1
"Graham is a boy"     0

लेकिन क्या होगा अगर आपने बूलियन को अपडेट किया?

public enum Boolean {
    TRUE, FILE_NOT_FOUND, FALSE
}

इसका मतलब है कि आपके सभी झूठ 'फ़ाइल-नहीं-पाया' के रूप में गलत हो जाएंगे

सिर्फ एक स्ट्रिंग प्रतिनिधित्व का उपयोग करने के लिए बेहतर है


4

एक बड़े डेटाबेस के लिए, मैं संख्यात्मक प्रतिनिधित्व के आकार और गति के लाभ को खोने के लिए अनिच्छुक हूं। मैं अक्सर Enum का प्रतिनिधित्व करने वाली एक डेटाबेस तालिका के साथ समाप्त होता हूं।

आप एक विदेशी कुंजी घोषित करके डेटाबेस संगतता लागू कर सकते हैं - हालांकि कुछ मामलों में यह घोषित करना बेहतर नहीं हो सकता है कि विदेशी कुंजी बाधा के रूप में, जो प्रत्येक लेनदेन पर एक लागत लगाता है। आप समय-समय पर एक चेक, अपने चयन के साथ, कर सकते हैं:

SELECT reftable.* FROM reftable
  LEFT JOIN enumtable ON reftable.enum_ref_id = enumtable.enum_id
WHERE enumtable.enum_id IS NULL;

इस समाधान का दूसरा भाग कुछ परीक्षण कोड लिखना है जो यह जाँचता है कि जावा एनम और डेटाबेस एनम टेबल में समान सामग्री है। यह पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है।


1
मान लें कि औसत गणना नाम की लंबाई 7 वर्ण है। आपकी enumIDचार बाइट्स हैं, इसलिए आपके पास नामों का उपयोग करके प्रति पंक्ति में एक अतिरिक्त तीन बाइट्स हैं। 3 बाइट्स x 1 मिलियन पंक्तियाँ 3MB है।
इयान बॉयड

@ इयानॉयड: लेकिन enumIdदो बाइट्स में निश्चित रूप से फिट बैठता है (जावा में लंबे समय तक एनम संभव नहीं है) और उनमें से ज्यादातर एक बाइट में फिट होते हैं (जो कुछ डीबी समर्थन करते हैं)। सहेजी गई जगह नगण्य है, लेकिन तेजी से तुलना और निर्धारित लंबाई में मदद करनी चाहिए।
Maaartinus

3

हम एनम नाम को ही स्टोर करते हैं - यह अधिक पठनीय है।

हमने एनमों के लिए विशिष्ट मानों को संचय करने के साथ गड़बड़ कर दिया है जहां सीमित मान हैं, उदाहरण के लिए, इस एनम में उन स्थितियों का एक सीमित सेट है जो हम प्रतिनिधित्व करने के लिए एक चार का उपयोग करते हैं (एक संख्यात्मक मान से अधिक सार्थक):

public enum EmailStatus {
    EMAIL_NEW('N'), EMAIL_SENT('S'), EMAIL_FAILED('F'), EMAIL_SKIPPED('K'), UNDEFINED('-');

    private char dbChar = '-';

    EmailStatus(char statusChar) {
        this.dbChar = statusChar;
    }

    public char statusChar() {
        return dbChar;
    }

    public static EmailStatus getFromStatusChar(char statusChar) {
        switch (statusChar) {
        case 'N':
            return EMAIL_NEW;
        case 'S':
            return EMAIL_SENT;
        case 'F':
            return EMAIL_FAILED;
        case 'K':
            return EMAIL_SKIPPED;
        default:
            return UNDEFINED;
        }
    }
}

और जब आपके पास बहुत सारे मान हों, तो आपको उस enFromXYZ पद्धति को छोटा रखने के लिए अपने enum के अंदर एक मानचित्र होना चाहिए।


यदि आप स्विच स्टेटमेंट को बनाए रखना नहीं चाहते हैं और यह सुनिश्चित कर सकते हैं कि dbChar अद्वितीय है तो आप कुछ इस तरह का उपयोग कर सकते हैं: सार्वजनिक स्थैतिक EmailStatus getFromStatusChar (char statusChar) {return Arrays.stream (EmailStatus-values ​​()) .filter (e ->)। e.statusChar () == statusChar) .findFirst () .orElse (UNDEFARED); }
कूची

2

अगर डेटाबेस में स्ट्रिंग्स के रूप में एनम की बचत होती है, तो आप किसी भी एनम को सीरीज़ करने के लिए यूटिलिटी मेथड बना सकते हैं:

   public static String getSerializedForm(Enum<?> enumVal) {
        String name = enumVal.name();
        // possibly quote value?
        return name;
    }

    public static <E extends Enum<E>> E deserialize(Class<E> enumType, String dbVal) {
        // possibly handle unknown values, below throws IllegalArgEx
        return Enum.valueOf(enumType, dbVal.trim());
    }

    // Sample use:
    String dbVal = getSerializedForm(Suit.SPADE);
    // save dbVal to db in larger insert/update ...
    Suit suit = deserialize(Suit.class, dbVal);

डिसेरिएलाइज़ में वापस गिरने के लिए एक डिफ़ॉल्ट एनम मान के साथ इसका उपयोग करना अच्छा है। उदाहरण के लिए, IllegalArgEx को पकड़ें और सूट को वापस करें। कोई भी नहीं।
जेसन

2

मेरा सारा अनुभव मुझे बताता है कि कहीं भी शत्रु को बनाए रखने का सबसे सुरक्षित तरीका अतिरिक्त कोड मूल्य या आईडी (@jeebee उत्तर का विकास) है। यह एक विचार का एक अच्छा उदाहरण हो सकता है:

enum Race {
    HUMAN ("human"),
    ELF ("elf"),
    DWARF ("dwarf");

    private final String code;

    private Race(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

अब आप किसी भी दृढ़ता के साथ अपने एनम स्थिरांक को कोड करके जा सकते हैं। यहां तक ​​कि अगर आप कुछ निरंतर नामों को बदलने का निर्णय लेंगे, तो आप हमेशा कोड मूल्य (जैसे) DWARF("dwarf")को बचा सकते हैंGNOME("dwarf") )

ठीक है, इस गर्भाधान के साथ कुछ और गहरा गोता लगाएँ। यहां कुछ उपयोगिता विधि है, जो आपको किसी भी एनम मान को खोजने में मदद करती है, लेकिन पहले हमारे दृष्टिकोण का विस्तार करती है।

interface CodeValue {
    String getCode();
}

और हमारे एनम को इसे लागू करने दें:

enum Race implement CodeValue {...}

यह जादू खोज विधि का समय है:

static <T extends Enum & CodeValue> T resolveByCode(Class<T> enumClass, String code) {
    T[] enumConstants = enumClass.getEnumConstants();
    for (T entry : enumConstants) {
        if (entry.getCode().equals(code)) return entry;
    }
    // In case we failed to find it, return null.
    // I'd recommend you make some log record here to get notified about wrong logic, perhaps.
    return null;
}

और इसे एक आकर्षण की तरह उपयोग करें: Race race = resolveByCode(Race.class, "elf")


2

मैंने उसी मुद्दे का सामना किया है जहां मेरा उद्देश्य साधारण मूल्य के बजाय एनम स्ट्रिंग मूल्य को डेटाबेस में बनाए रखना है।

इस मुद्दे पर आने के लिए, मैंने उपयोग किया है @Enumerated(EnumType.STRING)और मेरा उद्देश्य हल हो गया है।

उदाहरण के लिए, आपके पास एक Enumवर्ग है:

public enum FurthitMethod {

    Apple,
    Orange,
    Lemon
}

इकाई वर्ग में, परिभाषित करें @Enumerated(EnumType.STRING):

@Enumerated(EnumType.STRING)
@Column(name = "Fruits")
public FurthitMethod getFuritMethod() {
    return fruitMethod;
}

public void setFruitMethod(FurthitMethod authenticationMethod) {
    this.fruitMethod= fruitMethod;
}

जब आप अपना मान डेटाबेस पर सेट करने का प्रयास करते हैं, तो स्ट्रिंग मूल्य को " APPLE", " ORANGE" या " LEMON" के रूप में डेटाबेस में रखा जाएगा ।



0

आप enum स्थिरांक में एक अतिरिक्त मान का उपयोग कर सकते हैं, जो नाम के बदलाव और सहारा दोनों को जीवित कर सकता है:

public enum MyEnum {
    MyFirstValue(10),
    MyFirstAndAHalfValue(15),
    MySecondValue(20);

    public int getId() {
        return id;
    }
    public static MyEnum of(int id) {
        for (MyEnum e : values()) {
            if (id == e.id) {
                return e;
            }
        }
        return null;
    }
    MyEnum(int id) {
        this.id = id;
    }
    private final int id;
}

ईम से आईडी प्राप्त करने के लिए:

int id = MyFirstValue.getId();

एक आईडी से एनम प्राप्त करने के लिए:

MyEnum e = MyEnum.of(id);

मैं सुझाव देता हूं कि भ्रम से बचने के लिए बिना अर्थ के मूल्यों का उपयोग किया जाए, यदि एनम नामों को बदलना है।

उपरोक्त उदाहरण में, मैंने रिक्त स्थान छोड़ने वाले "बेसिक रो नंबरिंग" के कुछ वेरिएंट का उपयोग किया है ताकि संख्या संभवतः उसी क्रम में रहेगी जैसे कि एनम।

यह संस्करण द्वितीयक तालिका का उपयोग करने की तुलना में तेज़ है, लेकिन यह सिस्टम को कोड और स्रोत कोड ज्ञान पर अधिक निर्भर बनाता है।

यह उपाय करने के लिए, आप डेटाबेस में enum ids के साथ एक तालिका भी सेट कर सकते हैं। या दूसरे रास्ते पर जाएं और एक पंक्ति से एनमों के लिए आईडी चुनें क्योंकि आप इसमें पंक्तियाँ जोड़ते हैं।

सिडेनोट : हमेशा यह सत्यापित करें कि आप किसी ऐसी चीज़ को डिज़ाइन नहीं कर रहे हैं जिसे डेटाबेस तालिका में संग्रहीत किया जाना चाहिए और हालांकि एक नियमित ऑब्जेक्ट के रूप में बनाए रखा जाना चाहिए। यदि आप कल्पना कर सकते हैं कि आपको इस बिंदु पर एनम में नए स्थिरांक जोड़ना है, जब आप इसे स्थापित कर रहे हैं, तो यह एक संकेत है कि आप एक नियमित ऑब्जेक्ट और टेबल बनाने के बजाय बेहतर हो सकते हैं।

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