public static void Constructor(string name, string thisKey, string otherKey, string[] thisKeyMembers, string[] otherKeyMembers) { var attribute = new AssociationAttribute(name, thisKey, otherKey); Assert.Equal(name, attribute.Name); Assert.Equal(thisKey, attribute.ThisKey); Assert.Equal(otherKey, attribute.OtherKey); if (thisKey == null) { Assert.Throws <NullReferenceException>(() => attribute.ThisKeyMembers); } else { Assert.Equal(thisKeyMembers, attribute.ThisKeyMembers); } if (otherKey == null) { Assert.Throws <NullReferenceException>(() => attribute.OtherKeyMembers); } else { Assert.Equal(otherKeyMembers, attribute.OtherKeyMembers); } }
public static void Can_construct_attribute_and_get_whitespace_values() { var attribute = new AssociationAttribute(null, string.Empty, " \t\r\n"); Assert.Equal(null, attribute.Name); Assert.Equal(string.Empty, attribute.ThisKey); Assert.Equal(" \t\r\n", attribute.OtherKey); }
public static void Can_construct_attribute_and_get_values() { var attribute = new AssociationAttribute("TestName", "TestThisKey", "TestOtherKey"); Assert.Equal("TestName", attribute.Name); Assert.Equal("TestThisKey", attribute.ThisKey); Assert.Equal("TestOtherKey", attribute.OtherKey); }
public static void Can_get_and_set_IsForeignKey() { var attribute = new AssociationAttribute("Name", "ThisKey", "OtherKey"); Assert.Equal(false, attribute.IsForeignKey); attribute.IsForeignKey = true; Assert.Equal(true, attribute.IsForeignKey); attribute.IsForeignKey = false; Assert.Equal(false, attribute.IsForeignKey); }
public static void Can_get_ThisKeyMembers_and_OtherKeyMembers() { var listOfThisKeys = new List<string>() { "ThisKey1", "ThisKey2", "ThisKey3" }; var listOfOtherKeys = new List<string>() { "OtherKey1", "OtherKey2" }; // doesn't matter how many spaces are between keys, but they must be separated by a comma var attribute = new AssociationAttribute("Name", "ThisKey1, ThisKey2, ThisKey3", "OtherKey1, OtherKey2"); Assert.True(listOfThisKeys.SequenceEqual(attribute.ThisKeyMembers)); Assert.True(listOfOtherKeys.SequenceEqual(attribute.OtherKeyMembers)); }
private void AddAttachMethod(Type linkTableType, AssociationAttribute assocAttrThisEnd, AssociationAttribute assocAttrOtherEnd, PropertyDescriptor prop1, PropertyDescriptor prop2) { var linkTableFullTypeName = linkTableType.FullName; var navProp1TypeFullName = prop1.PropertyType.FullName; var navProp2TypeName = prop2.PropertyType.Name; var navProp2TypeFullName = prop2.PropertyType.FullName; var navProp1NameLower = ToLowerInitial(prop1.Name); var navProp2NameLower = ToLowerInitial(prop2.Name); var thisKeyMembers = assocAttrThisEnd.ThisKeyMembers.ToList(); var otherKeyMembers = assocAttrThisEnd.OtherKeyMembers.ToList(); WriteLine(@"#region Lines added by m2m4ria code generator"); WriteLine(@"/// <summary>"); WriteLine( @"/// This method attaches {0} and {1} to the current link table entity, in such a way", navProp1TypeFullName, navProp2TypeFullName); WriteLine(@"/// that both navigation properties are set before an INotifyCollectionChanged event is fired."); WriteLine(@"/// </summary>"); WriteLine(@"/// <param name=""r""/>"); WriteLine(@"/// <param name=""{0}""/>", navProp1NameLower); WriteLine(@"/// <param name=""{0}""/>", navProp2NameLower); WriteLine( @"[System.ObsoleteAttribute(""This property is only intended for use by the M2M4Ria solution."")]"); WriteLine( @"public static void Attach{0}To{1}({2} r, {3} {4}, {5} {6})", navProp2TypeName, assocAttrOtherEnd.Name, linkTableFullTypeName, navProp1TypeFullName, navProp1NameLower, navProp2TypeFullName, navProp2NameLower); WriteLine(@"{"); WriteLine(@" var dummy = r.{0}; // this is to instantiate the EntityRef<{0}>", prop2.Name); WriteLine(@" r._{0}.Entity = {0};", navProp2NameLower); for(var i = 0; i < thisKeyMembers.Count(); i++) { var thisKeyLower = ToLowerInitial(thisKeyMembers[i]); WriteLine(@" r._{0} = {1}.{2};", thisKeyLower, navProp2NameLower, otherKeyMembers[i]); } WriteLine(@" r.{0} = {1};", prop1.Name, navProp1NameLower); WriteLine(@" r._{0}.Entity = null;", navProp2NameLower); for(var i = 0; i < thisKeyMembers.Count(); i++) { var thisKeyLower = ToLowerInitial(thisKeyMembers[i]); var keyType = linkTableType.GetProperty(thisKeyMembers[i]).PropertyType; WriteLine(@" r._{0} = default({1});", thisKeyLower, keyType.FullName); } WriteLine(@" r.{0} = {1};", prop2.Name, navProp2NameLower); WriteLine(@"}"); WriteLine(@"#endregion"); }
private static string GetLinkTableOtherNavigationPropertyName( AssociationAttribute association, Type linkTableType) { var properties = from p in TypeDescriptor.GetProperties(linkTableType).OfType<PropertyDescriptor>() let x = p.Attributes[typeof(AssociationAttribute)] as AssociationAttribute where x != null where x.Name != association.Name select p; return properties.Single().Name; }
public static void IsForeignKey_GetSet_ReturnsExpected() { var attribute = new AssociationAttribute("Name", "ThisKey", "OtherKey"); Assert.False(attribute.IsForeignKey); attribute.IsForeignKey = true; Assert.True(attribute.IsForeignKey); attribute.IsForeignKey = false; Assert.False(attribute.IsForeignKey); }
public void TestAssociationAttribute() { AssociationAttribute attr = new AssociationAttribute("name", "thisKey", "otherKey"); attr.IsForeignKey = false; Assert.AreEqual("name", attr.Name); Assert.AreEqual("thisKey", attr.ThisKey); Assert.AreEqual("otherKey", attr.OtherKey); Assert.AreEqual(false, attr.IsForeignKey); // Verify can reverse polarity of foreign key attr.IsForeignKey = true; Assert.AreEqual(true, attr.IsForeignKey); }
public static void Can_get_ThisKeyMembers_and_OtherKeyMembers() { var listOfThisKeys = new List <string>() { "ThisKey1", "ThisKey2", "ThisKey3" }; var listOfOtherKeys = new List <string>() { "OtherKey1", "OtherKey2" }; // doesn't matter how many spaces are between keys, but they must be separated by a comma var attribute = new AssociationAttribute("Name", "ThisKey1, ThisKey2, ThisKey3", "OtherKey1, OtherKey2"); Assert.True(listOfThisKeys.SequenceEqual(attribute.ThisKeyMembers)); Assert.True(listOfOtherKeys.SequenceEqual(attribute.OtherKeyMembers)); }
public static void Constructor(string name, string thisKey, string otherKey, string[] thisKeyMembers, string[] otherKeyMembers) { var attribute = new AssociationAttribute(name, thisKey, otherKey); Assert.Equal(name, attribute.Name); Assert.Equal(thisKey, attribute.ThisKey); Assert.Equal(otherKey, attribute.OtherKey); if (thisKey == null) { Assert.Throws<NullReferenceException>(() => attribute.ThisKeyMembers); } else { Assert.Equal(thisKeyMembers, attribute.ThisKeyMembers); } if (otherKey == null) { Assert.Throws<NullReferenceException>(() => attribute.OtherKeyMembers); } else { Assert.Equal(otherKeyMembers, attribute.OtherKeyMembers); } }
/// <summary> /// Returns a collection of all the <see cref="Attribute"/>s we infer from the metadata associated /// with the metadata member corresponding to the given property descriptor /// </summary> /// <param name="pd">A <see cref="PropertyDescriptor"/>.</param> /// <returns>A collection of attributes inferred from metadata in the given descriptor.</returns> protected override IEnumerable <Attribute> GetMemberAttributes(PropertyDescriptor pd) { List <Attribute> attributes = new List <Attribute>(); MetaDataMember member = this._metaType.DataMembers.Where(p => p.Name == pd.Name).SingleOrDefault(); if (member != null) { EditableAttribute editableAttribute = null; bool hasKeyAttribute = (pd.Attributes[typeof(KeyAttribute)] != null); if (member.IsPrimaryKey && !hasKeyAttribute) { attributes.Add(new KeyAttribute()); hasKeyAttribute = true; } // Check if the member is DB generated and add the DatabaseGeneratedAttribute to it if not already present. if (member.IsDbGenerated && pd.Attributes[typeof(DatabaseGeneratedAttribute)] == null) { if (member.AutoSync == AutoSync.OnInsert) { attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)); } else if (member.AutoSync == AutoSync.Always) { attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed)); } } if (hasKeyAttribute && !this._keyIsEditable) { editableAttribute = new EditableAttribute(false) { AllowInitialValue = true }; } if (member.IsAssociation && pd.Attributes[typeof(System.ComponentModel.DataAnnotations.AssociationAttribute)] == null) { System.ComponentModel.DataAnnotations.AssociationAttribute assocAttrib = this.TypeDescriptionContext.CreateAssociationAttribute(member); attributes.Add(assocAttrib); } // Add Required attribute to metdata if the member cannot be null and it is either a reference type or a Nullable<T> bool isStringType = pd.PropertyType == typeof(string) || pd.PropertyType == typeof(char[]); if (!member.CanBeNull && (!pd.PropertyType.IsValueType || IsNullableType(pd.PropertyType)) && pd.Attributes[typeof(RequiredAttribute)] == null) { attributes.Add(new RequiredAttribute()); } // Add implicit ConcurrencyCheck attribute to metadata if UpdateCheck is anything other than UpdateCheck.Never if (member.UpdateCheck != UpdateCheck.Never && pd.Attributes[typeof(ConcurrencyCheckAttribute)] == null) { attributes.Add(new ConcurrencyCheckAttribute()); } bool hasTimestampAttribute = (pd.Attributes[typeof(TimestampAttribute)] != null); if (member.IsVersion && !hasTimestampAttribute) { attributes.Add(new TimestampAttribute()); hasTimestampAttribute = true; } // All members marked with TimestampAttribute (inferred or explicit) need to // have [Editable(false)] applied if (hasTimestampAttribute && editableAttribute == null) { editableAttribute = new EditableAttribute(false); } // Add RoundtripOriginal attribute to this member unless // - this entity has a timestamp member, in which case that member should be the ONLY // member we apply RTO to. // - the member is marked with AssociationAttribute if (!member.IsAssociation && pd.Attributes[typeof(System.ComponentModel.DataAnnotations.AssociationAttribute)] == null && (this._metaType.VersionMember == null || member.IsVersion)) { if (pd.Attributes[typeof(RoundtripOriginalAttribute)] == null) { attributes.Add(new RoundtripOriginalAttribute()); } } if (isStringType && member.DbType != null && member.DbType.Length > 0 && pd.Attributes[typeof(StringLengthAttribute)] == null) { InferStringLengthAttribute(member.DbType, attributes); } // Add EditableAttribute if required if (editableAttribute != null && pd.Attributes[typeof(EditableAttribute)] == null) { attributes.Add(editableAttribute); } } return(attributes.ToArray()); }
public AssociationMetadata(PropertyDescriptor pd) { this.PropertyDescriptor = pd; AttributeCollection propertyAttributes = pd.ExplicitAttributes(); this.AssociationAttribute = (AssociationAttribute) propertyAttributes[typeof (AssociationAttribute)]; this.IsExternal = propertyAttributes[typeof (ExternalReferenceAttribute)] != null; this.IsCollection = EntityGenerator.IsCollectionType(pd.PropertyType); if (!this.IsCollection) { this.PropTypeName = CodeGenUtilities.GetTypeName(pd.PropertyType); this.AssociationTypeName = @"OpenRiaServices.DomainServices.Client.EntityRef<" + this.PropTypeName + ">"; this.Attributes = propertyAttributes.Cast<Attribute>().Where(a => a.GetType() != typeof (DataMemberAttribute)); } else { this.PropTypeName = CodeGenUtilities.GetTypeName(TypeUtility.GetElementType(pd.PropertyType)); this.AssociationTypeName = "OpenRiaServices.DomainServices.Client.EntityCollection<" + this.PropTypeName + ">"; List<Attribute> attributeList = propertyAttributes.Cast<Attribute>().ToList(); ReadOnlyAttribute readOnlyAttr = propertyAttributes.OfType<ReadOnlyAttribute>().SingleOrDefault(); if (readOnlyAttr != null && !propertyAttributes.OfType<EditableAttribute>().Any()) { attributeList.Add(new EditableAttribute(!readOnlyAttr.IsReadOnly)); } this.Attributes = attributeList.Where(a => a.GetType() != typeof (DataMemberAttribute)); } this.PropertyName = CodeGenUtilities.GetSafeName(pd.Name); this.FieldName = CodeGenUtilities.MakeCompliantFieldName(this.PropertyName); }
/// <summary> /// Sets the parent association info for this entity. /// </summary> /// <remarks> /// Since a Type can have multiple compositions of the same Type, to /// identify a parent association, we must track both the parent instance /// AND the association. /// </remarks> /// <param name="parent">The parent.</param> /// <param name="association">The parent association.</param> internal void SetParent(Entity parent, AssociationAttribute association) { if (this._parent != parent) { if (parent == this) { throw new InvalidOperationException(Resource.Entity_ChildCannotBeItsParent); } this._parent = parent; } this._parentAssociation = association; }
public void Constructor () { AssociationAttribute attr; attr = new AssociationAttribute (null, "key1,key2", "key3,key4"); Assert.AreEqual (null, attr.Name, "#A1-1"); Assert.AreEqual ("key1,key2", attr.ThisKey, "#A1-2"); Assert.AreEqual ("key3,key4", attr.OtherKey, "#A1-3"); Assert.IsNotNull (attr.OtherKeyMembers, "#A2-1"); int count = 0; var list = new List<string> (); foreach (string m in attr.OtherKeyMembers) { count++; list.Add (m); } Assert.AreEqual (2, count, "#A2-2"); Assert.AreEqual ("key3", list [0], "#A2-3"); Assert.AreEqual ("key4", list [1], "#A2-4"); Assert.IsNotNull (attr.ThisKeyMembers, "#A3-1"); count = 0; list = new List<string> (); foreach (string m in attr.ThisKeyMembers) { count++; list.Add (m); } Assert.AreEqual (2, count, "#A3-2"); Assert.AreEqual ("key1", list [0], "#A3-3"); Assert.AreEqual ("key2", list [1], "#A3-4"); attr = new AssociationAttribute ("name", null, "key3,key4"); Assert.AreEqual ("name", attr.Name, "#B1-1"); Assert.AreEqual (null, attr.ThisKey, "#B1-2"); Assert.AreEqual ("key3,key4", attr.OtherKey, "#B1-3"); Assert.IsNotNull (attr.OtherKeyMembers, "#B2-1"); count = 0; list = new List<string> (); foreach (string m in attr.OtherKeyMembers) { count++; list.Add (m); } Assert.AreEqual (2, count, "#B2-2"); Assert.AreEqual ("key3", list [0], "#B2-3"); Assert.AreEqual ("key4", list [1], "#B2-4"); // this is just sad... try { var m = attr.ThisKeyMembers; Assert.Fail ("#B2-5"); } catch (NullReferenceException) { // success } attr = new AssociationAttribute ("name", " key1 , key 2 ,, ,key 3 ", " "); Assert.IsNotNull (attr.ThisKeyMembers, "#C1"); count = 0; list = new List<string> (); foreach (string m in attr.ThisKeyMembers) { count++; list.Add (m); } // It seems all the whitespace is removed from key names Assert.AreEqual (5, count, "#C2-1"); Assert.AreEqual ("key1", list [0], "#C2-2"); Assert.AreEqual ("key2", list [1], "#C2-3"); Assert.AreEqual (String.Empty, list [2], "#C2-4"); Assert.AreEqual (String.Empty, list [3], "#C2-5"); Assert.AreEqual ("key3", list [4], "#C2-6"); Assert.IsNotNull (attr.OtherKeyMembers, "#C3"); count = 0; list = new List<string> (); foreach (string m in attr.OtherKeyMembers) { count++; list.Add (m); } Assert.AreEqual (1, count, "#C4-1"); Assert.AreEqual (String.Empty, list [0], "#C4-2"); }
internal static PropertyDescriptor GetReverseAssociation(PropertyDescriptor propertyDescriptor, AssociationAttribute assocAttrib) { Type otherType = TypeUtility.GetElementType(propertyDescriptor.PropertyType); foreach (PropertyDescriptor entityMember in TypeDescriptor.GetProperties(otherType)) { if (entityMember.Name == propertyDescriptor.Name) { // for self associations, both ends of the association are in // the same class and have the same name. Therefore, we need to // skip the member itself. continue; } AssociationAttribute otherAssocAttrib = entityMember.Attributes[typeof(AssociationAttribute)] as AssociationAttribute; if (otherAssocAttrib != null && otherAssocAttrib.Name == assocAttrib.Name) { return entityMember; } } return null; }
private void GenerateSingletonAssociation(CodeTypeDeclaration proxyClass, PropertyDescriptor pd, AssociationAttribute associationAttribute, bool isExternal) { CodeTypeReference propType = isExternal ? new CodeTypeReference(pd.PropertyType.FullName) { Options = CodeTypeReferenceOptions.GlobalReference } : CodeGenUtilities.GetTypeReference(pd.PropertyType, this.ClientProxyGenerator, proxyClass); // generate field: // private EntityRef<Product> _Product; CodeTypeReference fldType = CodeGenUtilities.GetTypeReference(TypeConstants.EntityRefTypeFullName, this.Type.Namespace, false); fldType.TypeArguments.Add(propType); CodeMemberField fld = new CodeMemberField(); fld.Attributes = MemberAttributes.Private; fld.Name = CodeGenUtilities.MakeCompliantFieldName(pd.Name); fld.Type = fldType; proxyClass.Members.Add(fld); // generate property: // public Product Product { get {...} set {...} } CodeMemberProperty prop = new CodeMemberProperty(); prop.Attributes = MemberAttributes.Public | MemberAttributes.Final; prop.Name = pd.Name; prop.Type = propType; prop.HasGet = true; prop.HasSet = true; // For the FK side of external associations, we generate a "reduced" setter that // does validation and synchronizes FK members if (isExternal && !associationAttribute.IsForeignKey) { prop.HasSet = false; } // Generate <summary> comment for property string format = prop.HasSet ? Resource.CodeGen_Entity_Singleton_Association_Property_Summary_Comment : Resource.CodeGen_Entity_Singleton_Association_ReadOnly_Property_Summary_Comment; string comment = string.Format(CultureInfo.CurrentCulture, format, pd.PropertyType.Name); prop.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, this.ClientProxyGenerator.IsCSharp)); // ---------------------------------------------------------------- // Propagate the custom attributes (except DataMember) // ---------------------------------------------------------------- AttributeCollection propertyAttributes = pd.ExplicitAttributes(); CustomAttributeGenerator.GenerateCustomAttributes( this.ClientProxyGenerator, proxyClass, ex => string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_ThrewException_CodeTypeMember, ex.Message, prop.Name, proxyClass.Name, ex.InnerException.Message), propertyAttributes.Cast<Attribute>().Where(a => a.GetType() != typeof(DataMemberAttribute)), prop.CustomAttributes, prop.Comments); // -------------------------- // Generate the filter method // -------------------------- // private bool filter_Product(Product entity) { // return entity.ProductID == ProductID; // } string[] thisKeyProps = associationAttribute.ThisKeyMembers.ToArray(); string[] otherKeyProps = associationAttribute.OtherKeyMembers.ToArray(); CodeMemberMethod filterMethod = this.GenerateFilterMethod(proxyClass, pd.Name, pd.PropertyType, thisKeyProps, otherKeyProps, isExternal); // -------------------------- // Generate getter // -------------------------- // generate delayed initialization // if (_Product == null) { // _Product = new EntityRef<Product>(this, filter_Product); // } CodeExpression entityExpr = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fld.Name); entityExpr = new CodePropertyReferenceExpression(entityExpr, "Entity"); CodeExpression isRefNullExpr = CodeGenUtilities.MakeEqualToNull( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fld.Name)); CodeExpression filterDelegate = CodeGenUtilities.MakeDelegateCreateExpression(this.ClientProxyGenerator.IsCSharp, new CodeTypeReference("System.Func"), filterMethod.Name); CodeAssignStatement initExpr = new CodeAssignStatement( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fld.Name), new CodeObjectCreateExpression( fldType, new CodeThisReferenceExpression(), new CodePrimitiveExpression(prop.Name), filterDelegate)); prop.GetStatements.Add(new CodeConditionStatement(isRefNullExpr, initExpr)); // generate : return _Product.Entity; prop.GetStatements.Add(new CodeMethodReturnStatement(entityExpr)); // -------------------------- // Generate setter // -------------------------- if (prop.HasSet) { PropertyDescriptor reverseAssociationMember = GetReverseAssociation(pd, associationAttribute); CodeStatement detachStatement = null; CodeStatement attachStatement = null; bool reverseIsSingleton = false; // we need to emit back-reference fixup code if this association is bi-directional, and the other side // of the association will also be generated (don't want to generate code that references non-existent // members) bool isBiDirectionalAssociation = (reverseAssociationMember != null) && this.CanGenerateProperty(reverseAssociationMember); if (!isExternal && isBiDirectionalAssociation) { // currently relying on our naming convention for association names to get the name // of the reverse collection property string revName = reverseAssociationMember.Name; reverseIsSingleton = !IsCollectionType(reverseAssociationMember.PropertyType); if (!reverseIsSingleton) { detachStatement = new CodeExpressionStatement( new CodeMethodInvokeExpression( new CodePropertyReferenceExpression( new CodeVariableReferenceExpression("previous"), revName), "Remove", new CodeThisReferenceExpression())); attachStatement = new CodeExpressionStatement( new CodeMethodInvokeExpression( new CodePropertyReferenceExpression( new CodePropertySetValueReferenceExpression(), revName), "Add", new CodeThisReferenceExpression())); } else { detachStatement = new CodeAssignStatement( new CodePropertyReferenceExpression( new CodeVariableReferenceExpression("previous"), revName), new CodePrimitiveExpression(null)); attachStatement = new CodeAssignStatement( new CodePropertyReferenceExpression( new CodePropertySetValueReferenceExpression(), revName), new CodeThisReferenceExpression()); } } // code to sync FK values from the new property value List<CodeStatement> statements1 = null; List<CodeStatement> statements2 = null; statements1 = new List<CodeStatement>(); if (associationAttribute.IsForeignKey) { // only emit FK sync code if this is a foreign key association for (int i = 0; i < thisKeyProps.Length; i++) { statements1.Add( new CodeAssignStatement( new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), thisKeyProps[i]), new CodePropertyReferenceExpression(new CodePropertySetValueReferenceExpression(), otherKeyProps[i]))); } // code to set FK values to default values if the new property value is null statements2 = new List<CodeStatement>(); for (int i = 0; i < thisKeyProps.Length; i++) { Type foreignKeyType = TypeDescriptor.GetProperties(this.Type).Find(thisKeyProps[i], false).PropertyType; statements2.Add( new CodeAssignStatement( new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), thisKeyProps[i]), new CodeDefaultValueExpression( CodeGenUtilities.GetTypeReference(foreignKeyType, this.ClientProxyGenerator, proxyClass)))); } } // if(previous != value) CodeExpression prevValueExpr = CodeGenUtilities.MakeNotEqual(null, new CodeVariableReferenceExpression("previous"), new CodePropertySetValueReferenceExpression(), this.ClientProxyGenerator.IsCSharp); // Product previous = Product; prop.SetStatements.Add(new CodeVariableDeclarationStatement(prop.Type, "previous", new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), pd.Name))); List<CodeStatement> stmts = new List<CodeStatement>(); // Generate the validation test CodeStatement validationCode = GeneratePropertySetterValidation(prop.Name); stmts.Add(validationCode); if (!isExternal && isBiDirectionalAssociation) { List<CodeStatement> detachStmts = new List<CodeStatement>(); // Generate : this._Product.Entity = null; // This prevents infinite recursion in 1:1 association case, // and in general ensures that change notifications don't get // raised for the ref and fk properties during the transition // time when the ref is set to null (detached) before setting // to the new value. detachStmts.Add(new CodeAssignStatement(entityExpr, new CodePrimitiveExpression(null))); // previous.PurchaseOrderDetails.Remove(this); detachStmts.Add(detachStatement); // if (v != null) { // . . . // } stmts.Add(new CodeConditionStatement(CodeGenUtilities.MakeNotEqualToNull(new CodeVariableReferenceExpression("previous")), detachStmts.ToArray())); } // this._Product.Entity = value CodeAssignStatement setEntityStmt = new CodeAssignStatement( entityExpr, new CodePropertySetValueReferenceExpression()); // if (value != null) CodeConditionStatement stmt; if (associationAttribute.IsForeignKey) { stmt = new CodeConditionStatement( CodeGenUtilities.MakeNotEqualToNull( new CodePropertySetValueReferenceExpression()), statements1.ToArray(), statements2.ToArray()); stmts.Add(stmt); if (!isExternal) { // for FK sides of an association, we must set the entity // reference AFTER FK member sync stmts.Add(setEntityStmt); // add the attach statement for bidirectional associations if (isBiDirectionalAssociation) { stmts.Add(new CodeConditionStatement( CodeGenUtilities.MakeNotEqualToNull(new CodePropertySetValueReferenceExpression()), attachStatement)); } } } else { stmts.Add(setEntityStmt); // add the attach statement for bidirectional associations if (isBiDirectionalAssociation) { stmts.Add(new CodeConditionStatement( CodeGenUtilities.MakeNotEqualToNull(new CodePropertySetValueReferenceExpression()), attachStatement)); } stmt = new CodeConditionStatement( CodeGenUtilities.MakeNotEqualToNull( new CodePropertySetValueReferenceExpression()), statements1.ToArray()); } if (!isExternal) { // Generate : this.RaisePropertyChanged(<propName>); stmts.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression( new CodeThisReferenceExpression(), "RaisePropertyChanged", new CodePrimitiveExpression(prop.Name)))); } prop.SetStatements.Add(new CodeConditionStatement(prevValueExpr, stmts.ToArray())); } proxyClass.Members.Add(prop); proxyClass.Members.Add(filterMethod); }
private void GenEntityAssocationProperty(CodeTypeDeclaration proxyClass, PropertyDescriptor pd, AssociationAttribute associationAttribute, bool isExternal) { // Register property types to prevent conflicts Type associationType = IsCollectionType(pd.PropertyType) ? TypeUtility.GetElementType(pd.PropertyType) : pd.PropertyType; // Check if we're in conflict if (!CodeGenUtilities.RegisterTypeName(associationType, this.Type.Namespace)) { // Aggressively check for potential conflicts across other DomainService entity types. IEnumerable<Type> potentialConflicts = this.ClientProxyGenerator.DomainServiceDescriptions .SelectMany<DomainServiceDescription, Type>(dsd => dsd.EntityTypes) .Where(entity => entity.Namespace == associationType.Namespace).Distinct(); foreach (Type potentialConflict in potentialConflicts) { // Check if we plan to include any types from this potential conflict's namespace CodeGenUtilities.RegisterTypeName(potentialConflict, this.Type.Namespace); } } if (!IsCollectionType(pd.PropertyType)) { this.GenerateSingletonAssociation(proxyClass, pd, associationAttribute, isExternal); } else { this.GenerateCollectionSideAssociation(proxyClass, pd, associationAttribute, isExternal); } }
/// <summary> /// Verify that the specified entity does not have any modified FK members for the /// specified composition. /// </summary> /// <param name="entity">The child entity to check.</param> /// <param name="compositionAttribute">The composition attribute.</param> private static void CheckInvalidChildUpdates(Entity entity, AssociationAttribute compositionAttribute) { if (compositionAttribute == null) { return; } if (entity.EntityState == EntityState.Modified && entity.ModifiedProperties.Select(p => p.Name).Intersect(compositionAttribute.OtherKeyMembers).Any()) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.Entity_CantReparentComposedChild, entity)); } }
/// <summary> /// Creates an AssociationAttribute for the specified navigation property /// </summary> /// <param name="navigationProperty">The navigation property that corresponds to the association (it identifies the end points)</param> /// <returns>A new AssociationAttribute that describes the given navigation property association</returns> internal AssociationAttribute CreateAssociationAttribute(NavigationProperty navigationProperty) { AssociationInfo assocInfo = this.GetAssociationInfo(navigationProperty); bool isForeignKey = navigationProperty.FromEndMember.Name == assocInfo.FKRole; string thisKey; string otherKey; if (isForeignKey) { thisKey = FormatMemberList(assocInfo.ThisKey); otherKey = FormatMemberList(assocInfo.OtherKey); } else { otherKey = FormatMemberList(assocInfo.ThisKey); thisKey = FormatMemberList(assocInfo.OtherKey); } AssociationAttribute assocAttrib = new AssociationAttribute(assocInfo.Name, thisKey, otherKey); assocAttrib.IsForeignKey = isForeignKey; return assocAttrib; }
protected virtual IEnumerable<Attribute> GetEntityMemberAttributes(PropertyDescriptor propertyDescriptor) { if (_classMetadata == null) return null; var attributes = new List<Attribute>(); //KeyAttributes if (_classMetadata.Identifier != null) { foreach (Column id in _identifierCols) { if (id.Name == propertyDescriptor.Name) { if (propertyDescriptor.Attributes[typeof(KeyAttribute)] == null) { attributes.Add(new KeyAttribute()); } if (propertyDescriptor.Attributes[typeof(EditableAttribute)] == null) { //An identifier is not editable, sometimes anyway it allow an initial value var editable = new EditableAttribute(false); if (id.Value is SimpleValue) editable.AllowInitialValue = "assigned".Equals(((SimpleValue)id.Value).IdentifierGeneratorStrategy, StringComparison.InvariantCultureIgnoreCase); attributes.Add(editable); } break; } } } Property member = _classMetadata.PropertyIterator.FirstOrDefault(x => x.Name == propertyDescriptor.Name); if (member == null) //If ther's no mapping in nhibernate... return attributes; //Required if ((!member.IsNullable) && (propertyDescriptor.PropertyType.IsValueType && (propertyDescriptor.Attributes[typeof(RequiredAttribute)] == null))) { attributes.Add(new RequiredAttribute()); } //Association if (member.Type.IsAssociationType && (propertyDescriptor.Attributes[typeof(AssociationAttribute)] == null)) { string name; string thisKey = ""; string otherkey = ""; if (member.Type.IsCollectionType) { name = propertyDescriptor.ComponentType.FullName + "_" + member.Name; if (member.Type.ReturnedClass.GetGenericArguments().Length != 1) { throw new Exception( String.Format( "The property {0} is not a generic collection as expected (like IList<T>)...", member.Name)); } Type targetClassType = member.Type.ReturnedClass.GetGenericArguments()[0]; foreach (Column col in _identifierCols) { thisKey += (thisKey != "" ? ", " : "") + col.Name; //*****Naming convention**** //Here I'm assuming that the name of each field in the type that holds the foreign key observe //the following structure: string field = member.Name.Replace(Inflector.Pluralize(targetClassType.Name), "") + propertyDescriptor.ComponentType.Name + "_" + col.Name; otherkey += (otherkey != "" ? ", " : "") + field; if (targetClassType.GetProperties(BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(x => x.Name == field) == null) throw new Exception(String.Format("The class {0} doesn't contain a Property named {1}", targetClassType.Name, field)); } } else //Member is a class type { //Key could be composite, cycle every identifier on "the other side" PersistentClass otherMappingClass = _nhibernateConfiguration.GetClassMapping(member.Type.ReturnedClass); foreach (Column col in otherMappingClass.Key.ColumnIterator) { //Naming Convention: //The name of each foreign key field be MUST BE the name of the class field + "_" + the name of the key field in the targetclass thisKey += (thisKey != "" ? ", " : "") + member.Name + "_" + col.Name; otherkey += (otherkey != "" ? ", " : "") + col.Name; } //Check: this name MUST ALWAYS BE the same on the both side of a bi-directional association name = member.Type.ReturnedClass.FullName + "_" + Inflector.Pluralize(member.PersistentClass.NodeName); } //CHECK: When do you want to add an IncludeAttribute ? if (!_classMetadata.IsLazy) { var incAttr = new IncludeAttribute(); attributes.Add(incAttr); } var attribute = new AssociationAttribute( name, thisKey, otherkey ); Type fromParent = ForeignKeyDirection.ForeignKeyFromParent.GetType(); attribute.IsForeignKey = fromParent.IsInstanceOfType(((IAssociationType)member.Type).ForeignKeyDirection); attributes.Add(attribute); } //RoundtripOriginal if (member == _classMetadata.Version) attributes.Add(new RoundtripOriginalAttribute()); return attributes.ToArray(); }
/// <summary> /// For the current ChangeSetEntry, this method returns all other entries in the changeset /// whose entity was previously a child in the specified composition association. /// </summary> /// <param name="association">The association to check.</param> /// <returns>The resulting collection of entries.</returns> private IEnumerable<ChangeSetEntry> FindOriginalChildren(AssociationAttribute association) { foreach (ChangeSetEntry entry in this._changeSetEntries.Where(p => p.Entity.EntityState == EntityState.Deleted)) { Entity parent = entry.Entity.Parent; if (parent == null) { // not a child entity continue; } if (parent == this._currentChangeSetEntry.Entity && entry.Entity.ParentAssociation.Name == association.Name) { // if the current entity is the original parent and the association // matches, return the entry yield return entry; } } }
private void GenerateCollectionSideAssociation(CodeTypeDeclaration proxyClass, PropertyDescriptor pd, AssociationAttribute associationAttribute, bool isExternal) { CodeTypeReference propType = CodeGenUtilities.GetTypeReference(TypeConstants.EntityCollectionTypeFullName, this.Type.Namespace, false); CodeTypeReference fldType; Type elementType = TypeUtility.GetElementType(pd.PropertyType); if (isExternal) { CodeTypeReference externalTypeRef = new CodeTypeReference(elementType.FullName); propType.TypeArguments.Add(externalTypeRef); // Note: we set the global flag *after* adding as a TypeArgument // to override the GenericTypeArgument flag. This is required // for proper global references. externalTypeRef.Options = CodeTypeReferenceOptions.GlobalReference; } else { propType.TypeArguments.Add( CodeGenUtilities.GetTypeReference( elementType, this.ClientProxyGenerator, proxyClass)); } fldType = propType; CodeMemberField fld = new CodeMemberField(); fld.Attributes = MemberAttributes.Private; fld.Name = CodeGenUtilities.MakeCompliantFieldName(pd.Name); fld.Type = fldType; proxyClass.Members.Add(fld); // -------------------------- // Generate the filter method // -------------------------- // private bool filter_PurchaseOrderDetails(PurchaseOrderDetail entity) { // return entity.ProductID == ProductID; // } string[] thisKeyProps = associationAttribute.ThisKeyMembers.ToArray(); string[] otherKeyProps = associationAttribute.OtherKeyMembers.ToArray(); CodeMemberMethod filterMethod = this.GenerateFilterMethod(proxyClass, pd.Name, elementType, thisKeyProps, otherKeyProps, isExternal); CodeMemberProperty prop = new CodeMemberProperty(); prop.Name = pd.Name; prop.Attributes = MemberAttributes.Public | MemberAttributes.Final; prop.Type = propType; prop.HasGet = true; // Generate <summary> comment for property string comment = string.Format(CultureInfo.CurrentCulture, Resource.CodeGen_Entity_Collection_Association_Property_Summary_Comment, elementType.Name); prop.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, this.ClientProxyGenerator.IsCSharp)); // ---------------------------------------------------------------- // Propagate the custom attributes (except DataMember) // ---------------------------------------------------------------- List<Attribute> propertyAttributes = pd.ExplicitAttributes().Cast<Attribute>().ToList(); // Here, we check for the existence of [ReadOnly(true)] attributes generated when // the property does not not have a setter. We want to inject an [Editable(false)] // attribute into the pipeline. ReadOnlyAttribute readOnlyAttr = propertyAttributes.OfType<ReadOnlyAttribute>().SingleOrDefault(); if (readOnlyAttr != null && !propertyAttributes.OfType<EditableAttribute>().Any()) { propertyAttributes.Add(new EditableAttribute(!readOnlyAttr.IsReadOnly)); // REVIEW: should we strip out [ReadOnly] attributes here? } CustomAttributeGenerator.GenerateCustomAttributes( this.ClientProxyGenerator, proxyClass, ex => string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Attribute_ThrewException_CodeTypeMember, ex.Message, prop.Name, proxyClass.Name, ex.InnerException.Message), propertyAttributes.Where(a => a.GetType() != typeof(DataMemberAttribute)), prop.CustomAttributes, prop.Comments); // Generate "if (fld == null)" test for common use below CodeExpression isRefNullExpr = CodeGenUtilities.MakeEqualToNull( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fld.Name)); // Generate a delegate style invoke of our filter method for common use below CodeExpression filterDelegate = CodeGenUtilities.MakeDelegateCreateExpression(this.ClientProxyGenerator.IsCSharp, new CodeTypeReference("System.Func"), filterMethod.Name); // only generate attach and detach when the relation is two-way PropertyDescriptor reverseAssociationMember = GetReverseAssociation(pd, associationAttribute); bool isBiDirectionalAssociation = (reverseAssociationMember != null) && this.CanGenerateProperty(reverseAssociationMember); if (isBiDirectionalAssociation) { if (IsCollectionType(pd.PropertyType)) { CodeMemberMethod attach = this.GenerateAttach(proxyClass, elementType, associationAttribute, pd); CodeMemberMethod detach = this.GenerateDetach(proxyClass, elementType, associationAttribute, pd); //// generate : //// if (_PurchaseOrderDetails == null) { //// _PurchaseOrderDetails = new EntityCollection<PurchaseOrderDetail>(this, filter_PurchaseOrderDetails, attach_PurchaseOrderDetails, detach_PurchaseOrderDetails); //// } CodeExpression attachDelegate = CodeGenUtilities.MakeDelegateCreateExpression(this.ClientProxyGenerator.IsCSharp, new CodeTypeReference("System.Action"), attach.Name); CodeExpression detachDelegate = CodeGenUtilities.MakeDelegateCreateExpression(this.ClientProxyGenerator.IsCSharp, new CodeTypeReference("System.Action"), detach.Name); CodeAssignStatement initExpr = new CodeAssignStatement( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), fld.Name), new CodeObjectCreateExpression( propType, new CodeThisReferenceExpression(), new CodePrimitiveExpression(prop.Name), filterDelegate, attachDelegate, detachDelegate)); prop.GetStatements.Add(new CodeConditionStatement(isRefNullExpr, initExpr)); } } else { CodeAssignStatement initExpr = new CodeAssignStatement( new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), fld.Name), new CodeObjectCreateExpression( propType, new CodeThisReferenceExpression(), new CodePrimitiveExpression(prop.Name), filterDelegate)); prop.GetStatements.Add(new CodeConditionStatement(isRefNullExpr, initExpr)); } prop.GetStatements.Add( new CodeMethodReturnStatement( new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fld.Name))); proxyClass.Members.Add(prop); proxyClass.Members.Add(filterMethod); }
public TypePropertyAssociationMetadata(AssociationAttribute associationAttr) { Name = associationAttr.Name; IsForeignKey = associationAttr.IsForeignKey; _otherKeyMembers = associationAttr.OtherKeyMembers.ToList<string>(); _thisKeyMembers = associationAttr.ThisKeyMembers.ToList<string>(); }
private CodeMemberMethod GenerateDetach(CodeTypeDeclaration proxyClass, Type entityType, AssociationAttribute assoc, PropertyDescriptor pd) { // detach method CodeMemberMethod method = new CodeMemberMethod(); method.Name = "Detach" + pd.Name; method.Attributes = MemberAttributes.Private; method.ReturnType = CodeGenUtilities.GetTypeReference(typeof(void), this.ClientProxyGenerator, proxyClass); CodeParameterDeclarationExpression pdef = new CodeParameterDeclarationExpression( CodeGenUtilities.GetTypeReference(entityType, this.ClientProxyGenerator, proxyClass), "entity"); method.Parameters.Add(pdef); CodeVariableReferenceExpression pref = new CodeVariableReferenceExpression(pdef.Name); PropertyDescriptor reverseAssociationMember = GetReverseAssociation(pd, assoc); string revName = reverseAssociationMember.Name; if (!IsCollectionType(pd.PropertyType)) { // pref.Prop.Remove(this) method.Statements.Add( new CodeExpressionStatement( new CodeMethodInvokeExpression( new CodePropertyReferenceExpression(pref, revName), "Remove", new CodeThisReferenceExpression()))); } else { // pref.Prop = null method.Statements.Add( new CodeAssignStatement( new CodePropertyReferenceExpression(pref, revName), new CodePrimitiveExpression(null))); } proxyClass.Members.Add(method); return method; }