1 //---------------------------------------------------------------------
2 // <copyright file="ReturnType.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System.Data.EntityModel.SchemaObjectModel
12 using System.Collections.Generic;
13 using System.Data.Entity;
14 using System.Data.Metadata.Edm;
15 using System.Diagnostics;
16 using System.Globalization;
20 using Som = System.Data.EntityModel.SchemaObjectModel;
22 class ReturnType : ModelFunctionTypeElement
24 private CollectionKind _collectionKind = CollectionKind.None;
25 private bool _isRefType;
26 private string _unresolvedEntitySet = null;
27 private bool _entitySetPathDefined = false;
28 private ModelFunctionTypeElement _typeSubElement = null;
29 private EntityContainerEntitySet _entitySet = null;
35 /// <param name="parentElement"></param>
36 internal ReturnType(Function parentElement)
39 _typeUsageBuilder = new TypeUsageBuilder(this);
45 internal bool IsRefType
47 get { return _isRefType; }
50 internal CollectionKind CollectionKind
52 get { return _collectionKind; }
55 internal EntityContainerEntitySet EntitySet
57 get { return _entitySet; }
60 internal bool EntitySetPathDefined
62 get { return _entitySetPathDefined; }
65 internal ModelFunctionTypeElement SubElement
67 get { return _typeSubElement; }
70 internal override TypeUsage TypeUsage
74 if (_typeSubElement != null)
76 return _typeSubElement.GetTypeUsage();
78 else if (_typeUsage != null)
82 else if (base.TypeUsage == null)
86 else if (_collectionKind != CollectionKind.None)
88 return TypeUsage.Create(new CollectionType(base.TypeUsage));
92 return base.TypeUsage;
99 internal override SchemaElement Clone(SchemaElement parentElement)
101 ReturnType parameter = new ReturnType((Function)parentElement);
102 parameter._type = _type;
103 parameter.Name = this.Name;
104 parameter._typeUsageBuilder = this._typeUsageBuilder;
105 parameter._unresolvedType = this._unresolvedType;
106 parameter._unresolvedEntitySet = this._unresolvedEntitySet;
107 parameter._entitySetPathDefined = this._entitySetPathDefined;
108 parameter._entitySet = this._entitySet;
112 protected override bool HandleAttribute(XmlReader reader)
114 if (base.HandleAttribute(reader))
118 else if (CanHandleAttribute(reader, XmlConstants.TypeElement))
120 HandleTypeAttribute(reader);
123 else if (CanHandleAttribute(reader, XmlConstants.EntitySet))
125 HandleEntitySetAttribute(reader);
128 else if (CanHandleAttribute(reader, XmlConstants.EntitySetPath))
130 HandleEntitySetPathAttribute(reader);
133 else if (_typeUsageBuilder.HandleAttribute(reader))
141 internal bool ResolveNestedTypeNames(Converter.ConversionCache convertedItemCache, Dictionary<Som.SchemaElement, GlobalItem> newGlobalItems)
143 Debug.Assert(_typeSubElement != null, "Nested type expected.");
144 return _typeSubElement.ResolveNameAndSetTypeUsage(convertedItemCache, newGlobalItems);
147 #region Private Methods
152 /// <param name="reader"></param>
153 private void HandleTypeAttribute(XmlReader reader)
155 Debug.Assert(reader != null);
156 Debug.Assert(UnresolvedType == null);
159 if (!Utils.GetString(Schema, reader, out type))
161 TypeModifier typeModifier;
163 Function.RemoveTypeModifier(ref type, out typeModifier, out _isRefType);
165 switch (typeModifier)
167 case TypeModifier.Array:
168 _collectionKind = CollectionKind.Bag;
171 Debug.Assert(typeModifier == TypeModifier.None, string.Format(CultureInfo.CurrentCulture, "Type is not valid for property {0}: {1}. The modifier for the type cannot be used in this context.", FQName, reader.Value));
175 if (!Utils.ValidateDottedName(Schema, reader, type))
178 UnresolvedType = type;
181 private void HandleEntitySetAttribute(XmlReader reader)
183 Debug.Assert(reader != null);
184 string entitySetName;
185 if (Utils.GetString(Schema, reader, out entitySetName))
187 _unresolvedEntitySet = entitySetName;
191 private void HandleEntitySetPathAttribute(XmlReader reader)
193 Debug.Assert(reader != null);
194 string entitySetPath;
195 if (Utils.GetString(Schema, reader, out entitySetPath))
197 // EF does not support this EDM 3.0 attribute, we only use it for validation.
198 _entitySetPathDefined = true;
202 protected override bool HandleElement(XmlReader reader)
204 if (base.HandleElement(reader))
208 else if (CanHandleElement(reader, XmlConstants.CollectionType))
210 HandleCollectionTypeElement(reader);
213 else if (CanHandleElement(reader, XmlConstants.ReferenceType))
215 HandleReferenceTypeElement(reader);
218 else if (CanHandleElement(reader, XmlConstants.TypeRef))
220 HandleTypeRefElement(reader);
223 else if (CanHandleElement(reader, XmlConstants.RowType))
225 HandleRowTypeElement(reader);
232 protected void HandleCollectionTypeElement(XmlReader reader)
234 Debug.Assert(reader != null);
236 var subElement = new CollectionTypeElement(this);
237 subElement.Parse(reader);
238 _typeSubElement = subElement;
241 protected void HandleReferenceTypeElement(XmlReader reader)
243 Debug.Assert(reader != null);
245 var subElement = new ReferenceTypeElement(this);
246 subElement.Parse(reader);
247 _typeSubElement = subElement;
250 protected void HandleTypeRefElement(XmlReader reader)
252 Debug.Assert(reader != null);
254 var subElement = new TypeRefElement(this);
255 subElement.Parse(reader);
256 _typeSubElement = subElement;
259 protected void HandleRowTypeElement(XmlReader reader)
261 Debug.Assert(reader != null);
263 var subElement = new RowTypeElement(this);
264 subElement.Parse(reader);
265 _typeSubElement = subElement;
270 internal override void ResolveTopLevelNames()
272 // If type was defined as an attribute: <ReturnType Type="int"/>
273 if (_unresolvedType != null)
275 base.ResolveTopLevelNames();
278 // If type was defined as a subelement: <ReturnType><CollectionType>...</CollectionType></ReturnType>
279 if (_typeSubElement != null)
281 Debug.Assert(!this.ParentElement.IsFunctionImport, "FunctionImports can't have sub elements in their return types, so we should NEVER see them here");
282 _typeSubElement.ResolveTopLevelNames();
285 if (this.ParentElement.IsFunctionImport && _unresolvedEntitySet != null)
287 ((FunctionImportElement)this.ParentElement).ResolveEntitySet(this, _unresolvedEntitySet, ref _entitySet);
291 internal override void Validate()
295 ValidationHelper.ValidateTypeDeclaration(this, _type, _typeSubElement);
296 ValidationHelper.ValidateFacets(this, _type, _typeUsageBuilder);
299 ValidationHelper.ValidateRefType(this, _type);
302 if (Schema.DataModel != SchemaDataModelOption.EntityDataModel)
304 Debug.Assert(Schema.DataModel == SchemaDataModelOption.ProviderDataModel ||
305 Schema.DataModel == SchemaDataModelOption.ProviderManifestModel, "Unexpected data model");
307 if (Schema.DataModel == SchemaDataModelOption.ProviderManifestModel)
309 // Only scalar return type is allowed for functions in provider manifest.
310 if (_type != null && (_type is ScalarType == false || _collectionKind != CollectionKind.None) ||
311 _typeSubElement != null && _typeSubElement.Type is ScalarType == false)
313 string typeName = "";
316 typeName = Function.GetTypeNameForErrorMessage(_type, _collectionKind, _isRefType);
318 else if (_typeSubElement != null)
320 typeName = _typeSubElement.FQName;
322 AddError(ErrorCode.FunctionWithNonEdmTypeNotSupported,
323 EdmSchemaErrorSeverity.Error,
325 System.Data.Entity.Strings.FunctionWithNonEdmPrimitiveTypeNotSupported(typeName, this.ParentElement.FQName));
328 else // SchemaDataModelOption.ProviderDataModel
330 Debug.Assert(Schema.DataModel == SchemaDataModelOption.ProviderDataModel, "Unexpected data model");
332 // In SSDL, function may only return a primitive type or a collection of rows.
335 // It is not possible to define a collection of rows via a type attribute, hence any collection is not allowed.
336 if (_type is ScalarType == false || _collectionKind != CollectionKind.None)
338 AddError(ErrorCode.FunctionWithNonPrimitiveTypeNotSupported,
339 EdmSchemaErrorSeverity.Error,
341 System.Data.Entity.Strings.FunctionWithNonPrimitiveTypeNotSupported(_isRefType ? _unresolvedType : _type.FQName, this.ParentElement.FQName));
344 else if (_typeSubElement != null)
346 if (_typeSubElement.Type is ScalarType == false)
348 if (Schema.SchemaVersion < XmlConstants.StoreVersionForV3)
350 // Before V3 provider model functions only supported scalar return types.
351 AddError(ErrorCode.FunctionWithNonPrimitiveTypeNotSupported,
352 EdmSchemaErrorSeverity.Error,
354 System.Data.Entity.Strings.FunctionWithNonPrimitiveTypeNotSupported(_typeSubElement.FQName, this.ParentElement.FQName));
358 // Starting from V3, TVFs must return collection of rows and row props can be only primitive types.
359 // The "collection of rows" is the only option in SSDL function ReturnType subelement thus it's enforced on the XSD level,
360 // so we can assume it here. The only thing we need to check is the type of the row properties.
361 var collection = _typeSubElement as CollectionTypeElement;
362 Debug.Assert(collection != null, "Can't find <CollectionType> inside TVF <ReturnType> element");
363 if (collection != null)
365 var row = collection.SubElement as RowTypeElement;
366 Debug.Assert(row != null, "Can't find <RowType> inside TVF <ReturnType><CollectionType> element");
369 if (row.Properties.Any(p => !p.ValidateIsScalar()))
371 AddError(ErrorCode.TVFReturnTypeRowHasNonScalarProperty,
372 EdmSchemaErrorSeverity.Error,
374 System.Data.Entity.Strings.TVFReturnTypeRowHasNonScalarProperty);
381 // else type is ScalarType which is supported in all version
386 if (_typeSubElement != null)
388 _typeSubElement.Validate();
392 internal override void WriteIdentity(StringBuilder builder) { }
394 internal override TypeUsage GetTypeUsage()
399 internal override bool ResolveNameAndSetTypeUsage(Converter.ConversionCache convertedItemCache, Dictionary<Som.SchemaElement, GlobalItem> newGlobalItems)
401 Debug.Fail("This method was not called from anywhere in the code before. If you got here you need to update this method and possibly ResolveNestedTypeNames()");