36

I am fighting to create a unique field EmailAddress. I've already seen in forums that I have to create an index, but it didn't work out for me so far. Does anyone have a code example? Do I have to create the index on every save/call, or is it enough to create it only once?

I tried this code:

DB.GetCollection<User>(Dbname)
    .EnsureIndex(new IndexKeysBuilder()
        .Ascending("EmailAddress"), IndexOptions.SetUnique(true));

DB.GetCollection<User>(Dbname).Save(user, SafeMode.True);

My User model looks like this:

public class User
{
    [Required(ErrorMessage = "Email Required")]
    public string EmailAddress { get; set; }

    public ObjectId Id { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }
}
0

9 Answers 9

31

The unique index only needs to be created once, after that any document inserts that contain a duplicate email address will fail. Here's an example:

var server = MongoServer.Create("mongodb://localhost");
var db = server.GetDatabase("myapp");

var users = db.GetCollection<User>("users");

users.EnsureIndex(new IndexKeysBuilder()
    .Ascending("EmailAddress"), IndexOptions.SetUnique(true));

var user1 = new User { EmailAddress = "[email protected]" };
var user2 = new User { EmailAddress = "[email protected]" };

try
{
    users.Save(user1, WriteConcern.Acknowledged);
    users.Save(user2, WriteConcern.Acknowledged);  // <-- throws MongoSafeModeException
}
catch (MongoSafeModeException ex)
{
    Console.WriteLine(ex.Message);
}
Sign up to request clarification or add additional context in comments.

Comments

28

EnsureIndex() is deprecated/obsolete per the C# mongo drivers version 2.0 spec: http://api.mongodb.org/csharp/current/html/M_MongoDB_Driver_MongoCollection_EnsureIndex_2.htm

heres how to do it async and via 2.0 code:

var mongoClient = new MongoClient("connection");
var db = mongoClient.GetDatabase("database");

var options = new CreateIndexOptions() { Unique = true };
var field = new StringFieldDefinition<User>("EmailAddress");
var indexDefinition = new IndexKeysDefinitionBuilder<User>().Ascending(field);
await db.GetCollection<Users>("users").Indexes.CreateOneAsync(indexDefinition, options);

In case of a non-string index:

   var options = new CreateIndexOptions() { Unique = true };
   IndexKeysDefinition<Foo> keyCode = "{ Code: 1 }";
   var codeIndexModel = new CreateIndexModel<Foo>(keyCode, options);

Comments

10

As of 2.8 below is the way to create a index. Please note last two lines. CreateOneAsync(indexDefinition, options) is obsolete.

var mongoClient = new MongoClient("connection");
var db = mongoClient.GetDatabase("database");

var options = new CreateIndexOptions() { Unique = true };
var field = new StringFieldDefinition<User>("EmailAddress");
var indexDefinition = new IndexKeysDefinitionBuilder<User>().Ascending(field);

var indexModel = new CreateIndexModel<User>(indexDefinition,options);
await db.GetCollection<Users>("users").Indexes.CreateOneAsync(indexModel);

1 Comment

You may want to use a builder and refer to fields by expressions rather than by strings so your code is safe when refactoring (renaming properties). See stackoverflow.com/a/68756542/2279059 or other answers for examples.
8

Your code looks right. Here's a full running program for you to compare against:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace TestEnsureIndexOnEmailAddress {
    public class User {
        public ObjectId Id;
        public string FirstName;
        public string LastName;
        public string EmailAddress;
    }

    public static class Program {
        public static void Main(string[] args) {
            var server = MongoServer.Create("mongodb://localhost/?safe=true");
            var database = server["test"];
            var users = database.GetCollection<User>("users");
            if (users.Exists()) { users.Drop(); }

            users.EnsureIndex(IndexKeys.Ascending("EmailAddress"), IndexOptions.SetUnique(true));
            var john = new User { FirstName = "John", LastName = "Smith", EmailAddress = "[email protected]" };
            users.Insert(john);
            var joe = new User { FirstName = "Joe", LastName = "Smith", EmailAddress = "[email protected]" };
            users.Insert(joe); // should throw exception
        }
    }
}

You can also use the mongo shell to confirm that the index got created:

> db.users.getIndexes()
[
        {
                "name" : "_id_",
                "ns" : "test.users",
                "key" : {
                        "_id" : 1
                },
                "v" : 0
        },
        {
                "_id" : ObjectId("4de8152ee447ad2550e3b5fd"),
                "name" : "EmailAddress_1",
                "ns" : "test.users",
                "key" : {
                        "EmailAddress" : 1
                },
                "unique" : true,
                "v" : 0
        }
]
>

1 Comment

Its working fine but i want to two or more field to reduce duplicate. Suggest me
3

In my opinion a better and more feasible way to do this is by creating a custom attribute, then with c# you could get the properties decorated with that attribute and add a unique index, this approach would be generic and very simple to use. Example below:

My custom attribute: MongoUniqueStringIndex

 public class DynamicFieldConfig
{
    public string InputType { get; set; }
    [MongoUniqueStringIndex]
    public string Name { get; set; }
    public string Label { get; set; }
    public List<string> Options { get; set; } = new List<string>();
    public string LookupTypeName { get; set; }
    public int Type { get; set; } //int to enum
    public string Value { get; set; }
    public List<DynamicFieldValidation> Validations { get; set; } = new List<DynamicFieldValidation>();
}

then where you get the collection, for example in my Data Access class I do this:

        public MongoDataAccess(IDatabaseSettings settings)
    {
        // todo: create a connection service/factory to get connection
        var client = new MongoClient(settings.ConnectionString);
        var database = client.GetDatabase(settings.DatabaseName);
        
        var entityName = new Pluralizer().Pluralize(typeof(TMongoEntity).Name);
        _entityStore = database.GetCollection<TMongoEntity>(entityName);

        var uniqueStringIndexProperties = typeof(TMongoEntity).GetProperties().Where(
            prop => Attribute.IsDefined(prop, typeof(MongoUniqueStringIndex))).ToList();
        if (uniqueStringIndexProperties.Any())
            foreach (var propertyInfo in uniqueStringIndexProperties)
            {
                var options = new CreateIndexOptions { Unique = true };
                var propertyInfoName = propertyInfo.Name;
                var field = new StringFieldDefinition<TMongoEntity>(propertyInfoName);
                var indexDefinition = new IndexKeysDefinitionBuilder<TMongoEntity>().Ascending(field);
                var indexModel = new CreateIndexModel<TMongoEntity>(indexDefinition, options);
                _entityStore.Indexes.CreateOne(indexModel);
            }
    }

Comments

1

here's a working program using MongoDB.Entities (disclaimer: I'm the author)

using System;
using MongoDB.Driver;
using MongoDB.Entities;

namespace StackOverflow
{
    public class User : Entity
    {
        public string Name { get; set; }
        public string EmailAddress { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new DB("test");

            DB.Index<User>()
              .Options(o => o.Unique = true, o => o.Background = false)
              .Key(u => u.EmailAddress, Type.Ascending)
              .Create();

            var user1 = new User { Name = "First User", EmailAddress = "[email protected]" };
            user1.Save();

            try
            {
                var user2 = new User { Name = "Second User", EmailAddress = "[email protected]" };
                user2.Save();
            }
            catch (MongoWriteException x)
            {
                Console.WriteLine(x.Message);
                Console.ReadKey();
            }
        }
    }
}

trying to create a second user with the same email address results in the following exception:

A write operation resulted in an error. E11000 duplicate key error collection: test.User index: EmailAddress(Asc) dup key: { : "[email protected]" }

Comments

1

You have to wrap your IndexKeysDefinition<TDocument> into a CreateIndexModel<TDocument> to be able to specify options, such as Unique = true. Example:

collection.Indexes.CreateOne(
  new CreateIndexModel<DbModel>(
    Builders<DbModel>.IndexKeys.Ascending(d => d.SomeField),
    new CreateIndexOptions { Unique = true }
));

See also https://stackoverflow.com/a/55210026/2279059.

Comments

1

All answers are missing on one crucial point is to never create the index if already there

var db = _mongoClient.GetDatabase("db");
var coll = db.GetCollection<Coll>("collName");
var indexes = (await (await coll.Indexes.ListAsync()).ToListAsync()).Select(_ => _.GetElement("name").Value.ToString()).ToList();
// If index not present create it else skip.
if (indexes.Where(_ => _.Equals("<Index Name>")).Count() == 0)
{
    // Create Index here
}

Comments

0

I'm using <PackageReference Include="MongoDB.Driver" Version="2.11.5" />

this is my code to create a unique index

var client = new MongoClient(mongourl);
var database = client.GetDatabase(dbName);

IMongoCollection<CollectionModel> myCollection = database.GetCollection<CollectionModel>("collection_name");

myCollection .Indexes.CreateOne(
      new CreateIndexModel<CollectionModel>(Builders<CollectionModel>.IndexKeys.Descending(model => model.YOUR_PROPERTY),
      new CreateIndexOptions { Unique = true }));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.