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 an
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@domain.com" };
user1.Save();
try
{
var user2 = new User { Name = "Second User", EmailAddress = "email@domain.com" };
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@domain.com" }
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);
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);
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 = "jsmith@xyz.com" };
users.Insert(john);
var joe = new User { FirstName = "Joe", LastName = "Smith", EmailAddress = "jsmith@xyz.com" };
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
}
]
>
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);
}
}
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 = "joe@example.com" };
var user2 = new User { EmailAddress = "joe@example.com" };
try
{
users.Save(user1, WriteConcern.Acknowledged);
users.Save(user2, WriteConcern.Acknowledged); // <-- throws MongoSafeModeException
}
catch (MongoSafeModeException ex)
{
Console.WriteLine(ex.Message);
}