diff --git a/NoRM.Tests/MongoCollectionTests.cs b/NoRM.Tests/MongoCollectionTests.cs index 163a859b..8f30e56c 100644 --- a/NoRM.Tests/MongoCollectionTests.cs +++ b/NoRM.Tests/MongoCollectionTests.cs @@ -382,8 +382,86 @@ public void InsertingANewEntityWithNullableIntGeneratesAKey() Assert.NotNull(testint._id); Assert.AreNotEqual(0, testint._id.Value); } + } + + + + [Test] + public void when_inserting_a_new_entity_with_int_id_equals_zero_generates_a_key() + { + using (var mongo = Mongo.Create(TestHelper.ConnectionString())) + { + var testint = new IntId {Name = "first"} ; + mongo.GetCollection("Fake").Insert(testint); + + Assert.NotNull(testint.Id); + Assert.AreNotEqual(0, testint.Id); + } + } + + [Test] + public void when_inserting_a_22_new_entities_with_int_id_equals_zero_generates_a_unique_key_for_each_of_them() + { + using (var mongo = Mongo.Create(TestHelper.ConnectionString())) + { + var idents = new List(); + for (int i = 0; i <22; i++) + { + var testint = new IntId() ; + mongo.GetCollection("Fake").Insert(testint); + idents.Add(testint.Id); + } + + var list = mongo.GetCollection("Fake").Find(new { _id = Q.In(idents.ToArray()) }); + + foreach (var item in list) + { + Assert.True(idents.Contains(item.Id)); + } + + Assert.AreEqual(idents.Distinct().Count(), list.Select(x => x.Id).Distinct().Count()); + } + } + + + [Test] + public void when_inserting_a_new_entity_with_long_type_id_equals_zero_generates_a_key() + { + using (var mongo = Mongo.Create(TestHelper.ConnectionString())) + { + var testint = new LongId() { Name = "first" }; + mongo.GetCollection("Fake").Insert(testint); + + Assert.NotNull(testint.Id); + Assert.AreNotEqual(0, testint.Id); + } + } + + [Test] + public void when_inserting_a_22_new_entities_with_long_type_id_equals_zero_generates_a_unique_key_for_each_of_them() + { + using (var mongo = Mongo.Create(TestHelper.ConnectionString())) + { + var idents = new List(); + for (int i = 0; i < 22; i++) + { + var testint = new LongId(); + mongo.GetCollection("Fake").Insert(testint); + idents.Add(testint.Id); + } + + var list = mongo.GetCollection("Fake").Find(new { _id = Q.In(idents.ToArray()) }); + + foreach (var item in list) + { + Assert.True(idents.Contains(item.Id)); + } + + Assert.AreEqual(idents.Distinct().Count(), list.Select(x => x.Id).Distinct().Count()); + } } + [Test] public void InsertingANewEntityWithNullableIntGeneratesAKeyComplex() { @@ -520,6 +598,8 @@ public void StringAsIdentifierDoesTranslation() } } + + private class StringIdentifier { [MongoIdentifier] @@ -531,6 +611,12 @@ private class IntId { public int Id { get; set; } public string Name { get; set; } + } + + private class LongId + { + public long Id { get; set; } + public string Name { get; set; } } } } \ No newline at end of file diff --git a/NoRM.Tests/MongoConfigurationTests.cs b/NoRM.Tests/MongoConfigurationTests.cs index b31bab8f..b83de724 100644 --- a/NoRM.Tests/MongoConfigurationTests.cs +++ b/NoRM.Tests/MongoConfigurationTests.cs @@ -224,8 +224,42 @@ public void Are_Queries_Fully_Linqified() Assert.AreEqual("Cart1", deepQuery[0].Cart.Name); Assert.AreEqual(1, deepQuery.Count); } - } - + } + + [Test] + public void should_ignore_name_property_when_inserting__as_specified_in_mappings() + { + + MongoConfiguration.Initialize(c => c.AddMap()); + using ( + Shoppers shoppers = + new Shoppers(Mongo.Create(TestHelper.ConnectionString("pooling=false", "test", null, null)))) + { + shoppers.Drop(); + shoppers.Add(new Shopper + { + Id = ObjectId.NewObjectId(), + Name = "John", + + }); + + shoppers.Add(new Shopper + { + Id = ObjectId.NewObjectId(), + Name = "Jane", + + }); + + + var deepQuery = shoppers.ToList(); + + Assert.IsNull(deepQuery[0].Name); + Assert.IsNull(deepQuery[1].Name); + + + } + } + [Test] public void Can_correctly_determine_collection_name() { diff --git a/NoRM.Tests/TestClasses.cs b/NoRM.Tests/TestClasses.cs index 920a4c5c..bbd153db 100644 --- a/NoRM.Tests/TestClasses.cs +++ b/NoRM.Tests/TestClasses.cs @@ -879,6 +879,25 @@ public ShopperMap() } } + public class ShopperMapWithIgnoreImmutableAndIgnoreIfNullConfigurationForProperties:MongoConfigurationMap + { + public ShopperMapWithIgnoreImmutableAndIgnoreIfNullConfigurationForProperties() + { + For( + config=> config.ForProperty(x => x.Name).Ignore() + ); + + For( + config=> + { + + config.ForProperty(x => x.Product).IgnoreIfNull(); + config.ForProperty(x => x.Name).Immutable(); + + } + ); + } + } internal class IdMap0 { public ObjectId _ID { get; set; } diff --git a/NoRM/BSON/MagicProperty.cs b/NoRM/BSON/MagicProperty.cs index 2ab64a55..aaa2d26a 100644 --- a/NoRM/BSON/MagicProperty.cs +++ b/NoRM/BSON/MagicProperty.cs @@ -1,8 +1,9 @@ using System; using System.Reflection; using Norm.Attributes; -using System.ComponentModel; - +using System.ComponentModel; +using Norm.Configuration; + namespace Norm.BSON { /// @@ -27,8 +28,11 @@ public class MagicProperty public MagicProperty(PropertyInfo property, Type declaringType) { _property = property; - this.IgnoreIfNull = property.GetCustomAttributes(_ignoredIfNullType, true).Length > 0; - this.Immutable = property.GetCustomAttributes(_immutableType, true).Length > 0; + this.IgnoreIfNull = property.GetCustomAttributes(_ignoredIfNullType, true).Length > 0 || + MongoConfiguration.IsPropertyIgnoredWhenNull(declaringType,property.Name); + this.Immutable = property.GetCustomAttributes(_immutableType, true).Length > 0 || + MongoConfiguration.IsPropertyImmutable(declaringType,property.Name); + var props = property.GetCustomAttributes(_defaultValueType, true); if (props.Length > 0) { diff --git a/NoRM/BSON/ReflectionHelper.cs b/NoRM/BSON/ReflectionHelper.cs index e15ecfcc..397e8d8b 100644 --- a/NoRM/BSON/ReflectionHelper.cs +++ b/NoRM/BSON/ReflectionHelper.cs @@ -244,7 +244,8 @@ private static IDictionary LoadMagicProperties(IEnumerabl foreach (var property in properties) { if (property.GetCustomAttributes(_ignoredType, true).Length > 0 || - property.GetIndexParameters().Length > 0) + property.GetIndexParameters().Length > 0|| + MongoConfiguration.IsPropertyIgnored(property.DeclaringType,property.Name)) { continue; } diff --git a/NoRM/Collections/CreateIndexExpression.cs b/NoRM/Collections/CreateIndexExpression.cs new file mode 100644 index 00000000..66e7ecbf --- /dev/null +++ b/NoRM/Collections/CreateIndexExpression.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq.Expressions; +using Norm.BSON; +using Norm.Configuration; +using Norm.Protocol.Messages; + +namespace Norm.Collections +{ + class CreateIndexExpression : ICreateIndexExpression + { + public Expando Expando + { + get; + set; + } + + public string CompoundName + { + get; + set; + } + + public CreateIndexExpression() + { + Expando = new Expando(); + + } + public void Index(Expression> func, IndexOption indexDirection) + { + var propName = this.RecurseExpression(func.Body); + Expando[propName] = indexDirection; + CompoundName += propName + "_" + (int)indexDirection; + + } + private String RecurseExpression(Expression body) + { + var me = body as MemberExpression; + if (me != null) + { + return this.RecurseMemberExpression(me); + } + + var ue = body as UnaryExpression; + if (ue != null) + { + return this.RecurseExpression(ue.Operand); + } + + throw new MongoException("Unknown expression type, expected a MemberExpression or UnaryExpression."); + } + private String RecurseMemberExpression(MemberExpression mex) + { + var retval = ""; + var parentEx = mex.Expression as MemberExpression; + if (parentEx != null) + { + //we need to recurse because we're not at the root yet. + retval += this.RecurseMemberExpression(parentEx) + "."; + } + retval += MongoConfiguration.GetPropertyAlias(mex.Expression.Type, mex.Member.Name); + return retval; + } + + } + + public interface ICreateIndexExpression + { + // Methods + void Index(Expression> func, IndexOption indexDirection); + + // Properties + string CompoundName { get; set; } + Expando Expando { get; set; } + } +} \ No newline at end of file diff --git a/NoRM/Collections/MongoCollectionGeneric.cs b/NoRM/Collections/MongoCollectionGeneric.cs index 830d9f93..a7519df8 100644 --- a/NoRM/Collections/MongoCollectionGeneric.cs +++ b/NoRM/Collections/MongoCollectionGeneric.cs @@ -67,7 +67,7 @@ public IQueryable AsQueryable() /// /// True if the type of this collection can be updated /// (i.e. the Type specifies "_id", "ID", or a property with the attributed "MongoIdentifier"). - /// + /// |?/**/ public bool Updateable { get @@ -106,11 +106,18 @@ public void Save(T entity) var helper = TypeHelper.GetHelperForType(typeof(T)); var idProperty = helper.FindIdProperty(); var id = idProperty.Getter(entity); - if (id == null && - (typeof(ObjectId).IsAssignableFrom(idProperty.Type)) || + if ((id == null && + ((typeof(ObjectId).IsAssignableFrom(idProperty.Type)) || (typeof(long?).IsAssignableFrom(idProperty.Type)) || - (typeof(int?).IsAssignableFrom(idProperty.Type)) ) + (typeof(int?).IsAssignableFrom(idProperty.Type)))) || + + (idProperty.Type == typeof(int) && id.Equals(0)) || + (idProperty.Type == typeof(long) && id.Equals((long)0)) + + ) { + + Insert(entity); } else @@ -545,7 +552,19 @@ public void CreateGeoIndex(Expression> index, IEnumerable> indexes, bool isUnique) + { + ICreateIndexExpression expression = new CreateIndexExpression(); + indexes(expression); + + this.CreateIndex(expression.Expando, expression.CompoundName, isUnique); + + } /// /// Gets the distinct values for the specified fieldSelectionExpando. @@ -879,19 +898,25 @@ private void TrySettingId(IEnumerable entities) Dictionary> knownTypes = new Dictionary> { { typeof(long?), () => GenerateId() }, { typeof(int?), () => Convert.ToInt32(GenerateId()) }, + { typeof(int), () => Convert.ToInt32(GenerateId()) }, + { typeof(long), () => GenerateId() }, { typeof(ObjectId), () => ObjectId.NewObjectId() } }; if (typeof(T) != typeof(Object) && typeof(T).GetInterface("IUpdateWithoutId") == null) { var idProperty = TypeHelper.GetHelperForType(typeof(T)).FindIdProperty(); + if (idProperty != null && knownTypes.ContainsKey(idProperty.Type) && idProperty.Setter != null) { foreach (var entity in entities) { var value = idProperty.Getter(entity); - if (value == null) + if (value == null || + (idProperty.Type==typeof(int) && value.Equals(0) )|| + idProperty.Type==typeof(long) && value.Equals((long)0)) { + idProperty.Setter(entity, knownTypes[idProperty.Type]()); } } diff --git a/NoRM/Configuration/IMongoConfigurationMap.cs b/NoRM/Configuration/IMongoConfigurationMap.cs index a98e80ac..8039cc4e 100644 --- a/NoRM/Configuration/IMongoConfigurationMap.cs +++ b/NoRM/Configuration/IMongoConfigurationMap.cs @@ -52,7 +52,14 @@ public interface IMongoConfigurationMap : IHideObjectMembers /// /// Type's property alias if configured; otherwise null /// - string GetPropertyAlias(Type type, string propertyName); + string GetPropertyAlias(Type type, string propertyName); + + bool IsPropertyIgnored(Type type, string propertyName); + + bool IsPropertyIgnoredWhenNull(Type type, string propertyName); + + bool IsPropertyImmutable(Type type, string propertyName); + string GetTypeDescriminator(Type type); } diff --git a/NoRM/Configuration/IPropertyMappingExpression.cs b/NoRM/Configuration/IPropertyMappingExpression.cs index 0c40dfe6..6d346232 100644 --- a/NoRM/Configuration/IPropertyMappingExpression.cs +++ b/NoRM/Configuration/IPropertyMappingExpression.cs @@ -18,6 +18,22 @@ public interface IPropertyMappingExpression : IHideObjectMembers /// /// The alias. /// - void UseAlias(string alias); + void UseAlias(string alias); + + /// + /// Ignores property if the value is null. + /// + void IgnoreIfNull(); + /// + /// Indicates that the BSON serializer should ignore property. + /// + void Ignore(); + + /// + /// Ignores property on updates, but not on inserts, i.e. this is write-once value + /// + /// + void Immutable(); + } } \ No newline at end of file diff --git a/NoRM/Configuration/MongoConfiguration.cs b/NoRM/Configuration/MongoConfiguration.cs index 9319f38f..6db7d3ea 100644 --- a/NoRM/Configuration/MongoConfiguration.cs +++ b/NoRM/Configuration/MongoConfiguration.cs @@ -101,8 +101,25 @@ public static void Initialize(Action action) public static string GetPropertyAlias(Type type, string propertyName) { return _configuration != null ? _configuration.GetConfigurationMap().GetPropertyAlias(type, propertyName) : propertyName; + } + + public static bool IsPropertyIgnored(Type type, string propertyName) + { + return _configuration != null ? _configuration.GetConfigurationMap().IsPropertyIgnored(type, propertyName) : false; + } + + public static bool IsPropertyIgnoredWhenNull(Type type, string propertyName) + { + return _configuration != null ? _configuration.GetConfigurationMap().IsPropertyIgnoredWhenNull(type, propertyName) : false; + } + + public static bool IsPropertyImmutable(Type type, string propertyName) + { + return _configuration != null ? _configuration.GetConfigurationMap().IsPropertyImmutable(type, propertyName) : false; } + + public static IBsonTypeConverter GetBsonTypeConverter(Type t) { return _configuration != null ? _configuration.GetTypeConverterFor(t) : null; diff --git a/NoRM/Configuration/MongoConfigurationMap.cs b/NoRM/Configuration/MongoConfigurationMap.cs index 1230515a..296bdaac 100644 --- a/NoRM/Configuration/MongoConfigurationMap.cs +++ b/NoRM/Configuration/MongoConfigurationMap.cs @@ -126,7 +126,7 @@ public string GetPropertyAlias(Type type, string propertyName) } else if (map.ContainsKey(type) && map[type].ContainsKey(propertyName)) { - retval = map[type][propertyName].Alias; + retval = map[type][propertyName].Alias??propertyName; } else if (discriminator != null && discriminator != type ) { @@ -135,8 +135,43 @@ public string GetPropertyAlias(Type type, string propertyName) retval = this.GetPropertyAlias(discriminator, propertyName); } return retval; - } - + } + + public bool IsPropertyIgnored(Type type, string propertyName) + { + var map = MongoTypeConfiguration.PropertyMaps; + var retValue = false; + if (map.ContainsKey(type) && map[type].ContainsKey(propertyName)) + { + retValue = map[type][propertyName].IsIgnored; + } + return retValue; + } + + public bool IsPropertyIgnoredWhenNull(Type type, string propertyName) + { + var map = MongoTypeConfiguration.PropertyMaps; + var retValue = false; + if (map.ContainsKey(type) && map[type].ContainsKey(propertyName)) + { + retValue = map[type][propertyName].IsIgnoredWhenNull; + } + return retValue; + } + + public bool IsPropertyImmutable(Type type, string propertyName) + { + var map = MongoTypeConfiguration.PropertyMaps; + var retValue = false; + if (map.ContainsKey(type) && map[type].ContainsKey(propertyName)) + { + retValue = map[type][propertyName].IsImmutable; + } + return retValue; + } + + + /// /// Gets the fluently configured discriminator type string for a type. /// diff --git a/NoRM/Configuration/MongoTypeConfiguration.cs b/NoRM/Configuration/MongoTypeConfiguration.cs index ad9835c7..b2aa0569 100644 --- a/NoRM/Configuration/MongoTypeConfiguration.cs +++ b/NoRM/Configuration/MongoTypeConfiguration.cs @@ -14,6 +14,7 @@ public class MongoTypeConfiguration private static readonly Dictionary _summaryTypes = new Dictionary(); private static readonly Dictionary _discriminatedTypes = new Dictionary(); + /// /// Gets the property maps. /// diff --git a/NoRM/Configuration/PropertyMappingExpression.cs b/NoRM/Configuration/PropertyMappingExpression.cs index e4199570..fe2b9b10 100644 --- a/NoRM/Configuration/PropertyMappingExpression.cs +++ b/NoRM/Configuration/PropertyMappingExpression.cs @@ -1,4 +1,6 @@  +using System; + namespace Norm.Configuration { /// @@ -11,6 +13,9 @@ public class PropertyMappingExpression : IPropertyMappingExpression /// /// The alias. internal string Alias { get; set; } + internal bool IsIgnored { get; set; } + internal bool IsIgnoredWhenNull { get; set; } + internal bool IsImmutable { get; set; } /// /// Gets or sets whether the property is the Id for the entity. @@ -33,6 +38,21 @@ public class PropertyMappingExpression : IPropertyMappingExpression public void UseAlias(string alias) { Alias = alias; - } + } + + public void IgnoreIfNull() + { + IsIgnoredWhenNull = true; + } + + public void Ignore() + { + IsIgnored = true; + } + + public void Immutable() + { + IsImmutable = true; + } } } \ No newline at end of file