Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Matthieu MEZIL

I love .Net

Abonnements

Actualités

Locations of visitors to this page English blog
Locations of visitors to this blog
Avoir une propriété sur l'object context qui renvoit les sous-entités v2

Suite aux conseils de Danniel Simmons, j'ai réussi à faire mon générateur en une seule passe.

public class SampleEdmxCodeGenerator : BaseCodeGeneratorWithSite

{

    private EntityContainer _objectContext;

    private Dictionary<string, string> _entitySetNames;

    private Dictionary<string, List<string>> _typesHierarchyToAddInObjectContext;

 

    public Dictionary<string, string> EntitySetNames

    {

        get

        {

            if (_entitySetNames == null)

                _entitySetNames = new Dictionary<string, string>();

            return _entitySetNames;

        }

    }

    private Dictionary<string, List<string>> TypesHierarchyToAddInObjectContext

    {

        get

        {

            if (_typesHierarchyToAddInObjectContext == null)

                _typesHierarchyToAddInObjectContext = new Dictionary<string, List<string>>();

            return _typesHierarchyToAddInObjectContext;

        }

    }

    private IEnumerable<string> GetSubEntitiesOf(EntityType baseType)

    {

        var empty = Enumerable.Empty<string>();

        if (_typesHierarchyToAddInObjectContext == null)

            return empty;

        string baseTypeName = baseType.Name;

        if (!_typesHierarchyToAddInObjectContext.ContainsKey(baseTypeName))

            return empty;

        return TypesHierarchyToAddInObjectContext[baseTypeName];

    }

    private void AddSubEntities(string baseType, string typeToAdd)

    {

        string baseTypeName = baseType;

        if (!TypesHierarchyToAddInObjectContext.ContainsKey(baseTypeName))

            TypesHierarchyToAddInObjectContext.Add(baseTypeName, new List<string>());

        TypesHierarchyToAddInObjectContext[baseTypeName].Add(typeToAdd);

    }

 

    protected override string GetDefaultExtension()

    {

        return (".Designer" + base.GetDefaultExtension());

    }

 

    protected override byte[] GenerateCode(string inputFileContent)

    {

        byte[] generatedCodeAsBytes = null;

 

        try

        {

            XElement csdlContent = ExtractCsdlContent(inputFileContent);

            if (csdlContent == null)

            {

                throw new InvalidOperationException("No CSDL content in input file");

            }

 

            _objectContext = null;

            _entitySetNames = null;

            _typesHierarchyToAddInObjectContext = null;

 

            LanguageOption languageOption = LanguageOption.GenerateCSharpCode;

            string fileExtension = base.GetCodeProvider().FileExtension;

            if (fileExtension != null && fileExtension.Length > 0)

            {

                fileExtension = "." + fileExtension.TrimStart(".".ToCharArray());

            }

            if (fileExtension.EndsWith(".vb", StringComparison.InvariantCultureIgnoreCase))

            {

                languageOption = LanguageOption.GenerateVBCode;

            }

            else if (fileExtension.EndsWith(".cs", StringComparison.InvariantCultureIgnoreCase))

            {

                languageOption = LanguageOption.GenerateCSharpCode;

            }

            else

            {

                throw new InvalidOperationException("Unsupported project language. Only C# and VB are supported.");

            }

 

            if (base.CodeGeneratorProgress != null)

            {

                base.CodeGeneratorProgress.Progress(33, 100);

            }

 

            EntityClassGenerator classGenerator;

            IList<EdmSchemaError> errors = null;

            using (StringWriter codeWriter = new StringWriter(CultureInfo.InvariantCulture))

            {

                using (XmlReader csdlReader = csdlContent.CreateReader())

                {

                    classGenerator = new EntityClassGenerator(languageOption);

                    classGenerator.OnTypeGenerated += new TypeGeneratedEventHandler(OnTypeGenerated);

                    classGenerator.OnPropertyGenerated += new PropertyGeneratedEventHandler(OnPropertyGenerated);

 

                    foreach (var entityType in from et in csdlContent.Descendants("{http://schemas.microsoft.com/ado/2006/04/edm}EntityType")

                                               let etBaseType = et.Attributes("BaseType").FirstOrDefault()

                                               where etBaseType != null

                                               select new { EntityType = et.Attribute("Name").Value, BaseType = etBaseType.Value })

                    {

                        var baseType = entityType.BaseType;

                        string baseTypeTmp;

                        while ((baseTypeTmp = (from et in csdlContent.Descendants("{http://schemas.microsoft.com/ado/2006/04/edm}EntityType")

                                               where et.Attribute("Name").Value == baseType

                                               let etBaseType = et.Attributes("BaseType").FirstOrDefault()

                                               where etBaseType != null

                                               select etBaseType.Value).FirstOrDefault()) != null)

                            baseTypeTmp = baseType;

                        Func<string, string> getSimpleName = entityTypeName => entityTypeName.Substring(entityTypeName.IndexOf(".") + 1);

                        AddSubEntities(getSimpleName(baseType), getSimpleName(entityType.EntityType));

                    }

 

                    errors = classGenerator.GenerateCode(csdlReader, codeWriter);

                }

                if (base.CodeGeneratorProgress != null)

                {

                    base.CodeGeneratorProgress.Progress(66, 100);

                }

                if (errors != null)

                {

                    foreach (EdmSchemaError error in errors)

                    {

                        int line = (error.Line == 0) ? 0 : (error.Line - 1);

                        int column = (error.Column == 0) ? 0 : (error.Column - 1);

 

                        if (error.Severity == EdmSchemaErrorSeverity.Warning)

                        {

                            base.GeneratorWarning(0, error.Message, (uint)line, (uint)column);

                        }

                        else

                        {

                            base.GeneratorError(4, error.Message, (uint)line, (uint)column);

                        }

                    }

                }

 

                generatedCodeAsBytes = Encoding.UTF8.GetBytes(codeWriter.ToString());

            }

            if (base.CodeGeneratorProgress != null)

            {

                base.CodeGeneratorProgress.Progress(100, 100);

            }

        }

        catch (Exception e)

        {

            base.GeneratorError(4, e.Message, 1, 1);

 

            generatedCodeAsBytes = null;

        }

 

        return generatedCodeAsBytes;

    }

 

    private void OnTypeGenerated(object sender, TypeGeneratedEventArgs eventArgs)

    {

        eventArgs.AdditionalAttributes.AddRange(CreateCodeAttributes(eventArgs.TypeSource));

 

        var objectContext = eventArgs.TypeSource as EntityContainer;

        if (objectContext != null)

        {

            _objectContext = objectContext;

            var baseEntitySets = _objectContext.MetadataProperties.FirstOrDefault(mp => mp.Name == "BaseEntitySets");

            if (baseEntitySets != null)

            {

                foreach (var entitySet in (ReadOnlyMetadataCollection<EntitySetBase>)baseEntitySets.Value)

                {

                    var derivedBaseEntityType = entitySet.ElementType as EntityType;

                    if (derivedBaseEntityType != null)

                    {

                        EntitySetNames.Add(derivedBaseEntityType.Name, entitySet.Name);

                        if (_typesHierarchyToAddInObjectContext != null)

                            foreach (var derivedEntityTypeName in GetSubEntitiesOf(derivedBaseEntityType))

                            {

                                var newProp = new CodeMemberProperty { Name = derivedEntityTypeName + "s", Attributes = MemberAttributes.Public | MemberAttributes.Final, Type = new CodeTypeReference("global::System.Linq.IQueryable<" + derivedEntityTypeName + ">") };

                                newProp.GetStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), _entitySetNames[derivedBaseEntityType.Name]), "OfType", new CodeTypeReference(derivedEntityTypeName)))));

                                eventArgs.AdditionalMembers.Add(newProp);

                            }

                    }

                }

            }

        }

    }

 

    private void OnPropertyGenerated(object sender, PropertyGeneratedEventArgs eventArgs)

    {

        eventArgs.AdditionalAttributes.AddRange(CreateCodeAttributes(eventArgs.PropertySource));

    }

 

    private IList<CodeAttributeDeclaration> CreateCodeAttributes(MetadataItem item)

    {

        string xmlns = "http://tempuri.org/AttributeAnnotations";

 

        List<CodeAttributeDeclaration> codeAttributeDeclarations = new List<CodeAttributeDeclaration>();

        if (item != null)

        {

            IEnumerable<MetadataProperty> metadataProperties = item.MetadataProperties.Where(prop => prop.Name.StartsWith(xmlns));

            foreach (MetadataProperty metadataProperty in metadataProperties)

            {

                string metadataPropertyValue = (string)metadataProperty.Value;

                if (!String.IsNullOrEmpty(metadataPropertyValue))

                {

                    string[] attributes = metadataPropertyValue.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string attribute in attributes)

                    {

                        string attributeName = attribute;

                        string[] attributeParams = new string[1];

 

                        if (attribute.Contains('('))

                        {

                            attributeParams = attribute.Split(new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries);

                            attributeName = attributeParams[0];

                        }

 

                        CodeAttributeDeclaration codeAttributeDeclaration = new CodeAttributeDeclaration(attributeName);

 

                        foreach (string attributeParam in attributeParams.Skip(1))

                        {

                            object attributeParamObj = null;

                            bool attributeParamBool = false;

                            if (bool.TryParse(attributeParam, out attributeParamBool))

                            {

                                attributeParamObj = attributeParamBool;

                            }

                            else

                            {

                                attributeParamObj = attributeParam;

                            }

                            codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(attributeParamObj)));

                        }

                        codeAttributeDeclarations.Add(codeAttributeDeclaration);

                    }

                }

            }

        }

        return codeAttributeDeclarations;

    }

 

    private XElement ExtractCsdlContent(string inputFileContent)

    {

        XElement csdlContent = null;

        XNamespace edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx";

        XNamespace edmns = "http://schemas.microsoft.com/ado/2006/04/edm";

 

        XDocument edmxDoc = XDocument.Load(new StringReader(inputFileContent));

        if (edmxDoc != null)

        {

            XElement edmxNode = edmxDoc.Element(edmxns + "Edmx");

            if (edmxNode != null)

            {

                XElement runtimeNode = edmxNode.Element(edmxns + "Runtime");

                if (runtimeNode != null)

                {

                    XElement conceptualModelsNode = runtimeNode.Element(edmxns + "ConceptualModels");

                    if (conceptualModelsNode != null)

                    {

                        csdlContent = conceptualModelsNode.Element(edmns + "Schema");

                    }

                }

            }

        }

        return csdlContent;

    }

}

Posté le vendredi 22 août 2008 01:11 par Matthieu MEZIL | 0 commentaire(s)

Classé sous : , , ,

Comment d&#233;bugger un programme de g&#233;n&#233;ration de code utilis&#233; dans VS ?

Dans mon cas, il s'agit de la customisation de mon générateur de code à partir de l'edmx, mais la méthode que je propose s'applique à bien d'autre cas.

Comment faire pour débuguer le CodeGen qui s'exécute directement dans VS ?

J'ai trouvé une petite feinte qui me plait bien Wink (il y a probablement une façon plus "conventionnelle" de faire ça).

Pour ma part, je met dans mon générateur de code un MessageBox.Show qui aura pour effet de suspendre la génération.

  • J'ouvre 2 VS : un pour le générateur de code et un pour utiliser mon programme de génération.
  • Dans le deuxième je fais appel à mon générateur (dans le cas du générateur de code à partir de l'edmx, il me suffit de changer le Custom Tool). La génération me lance la MessageBox.
  • De là, je retourne sur mon premier VS, j'attache le process DevEnv du deuxième VS, je met un point d'arrêt juste après le MessageBox.Show
  • Je retourne sur mon premier VS et je clique sur Ok dans ma MessageBox
  • Je retourne sur mon premier VS, je suis sur mon point d'arrêt et je peux maintenant débuguer dans mon générateur de code

Nice Wink

Posté le jeudi 21 août 2008 13:27 par Matthieu MEZIL | 5 commentaire(s)

Classé sous : , ,

Avoir une propriété sur l'object context qui renvoit les sous-entités

Dans l'entity framework, on a dans l'ObjectContext, une propriété par EntitySet. Le "problème" c'est que si on a une entité Person dérivée par une entité Employee, on n'a qu'un seul EntitySet : Persons.

Pour rajouter une propriété sur notre ObjectContext qui permettent de renvoyer uniquement les Employees, on peut rajouter à notre ObjectContext (partial) une propriété qui utilise la méthode OfType:

public IQueryable<Employee> Employees

{

    get

    {

        return Persons.OfType<Employee>();

    }

}

Jusque là facile.

Comme je l'ai dit sur ce thread du forum msdn hier, il est possible de le faire générer directement par l'outil de génération de code en le customisant.

En partant du code fournit par Elisa, voilà ce que j'ai fait:

public class SampleEdmxCodeGenerator : BaseCodeGeneratorWithSite

{

    private EntityContainer _objectContext;

    private Dictionary<string, EntitySetBase> _entitySetNames;

    private Dictionary<string, List<EntityType>> _typesHierarchyToAddInObjectContext;

    private bool _first = true;

 

    public Dictionary<string, EntitySetBase> EntitySetNames

    {

        get

        {

            if (_entitySetNames == null)

                _entitySetNames = new Dictionary<string, EntitySetBase>();

            return _entitySetNames;

        }

    }

    private Dictionary<string, List<EntityType>> TypesHierarchyToAddInObjectContext

    {

        get

        {

            if (_typesHierarchyToAddInObjectContext == null)

                _typesHierarchyToAddInObjectContext = new Dictionary<string, List<EntityType>>();

            return _typesHierarchyToAddInObjectContext;

        }

    }

    private IEnumerable<EntityType> GetSubEntitiesOf(EntityType baseType)

    {

        var empty = Enumerable.Empty<EntityType>();

        if (_typesHierarchyToAddInObjectContext == null)

            return empty;

        string baseTypeName = baseType.Name;

        if (!_typesHierarchyToAddInObjectContext.ContainsKey(baseTypeName))

            return empty;

        return TypesHierarchyToAddInObjectContext[baseTypeName];

    }

    private void AddSubEntities(EntityType baseType, EntityType typeToAdd)

    {

        string baseTypeName = baseType.Name;

        if (!TypesHierarchyToAddInObjectContext.ContainsKey(baseTypeName))

            TypesHierarchyToAddInObjectContext.Add(baseTypeName, new List<EntityType>());

        TypesHierarchyToAddInObjectContext[baseTypeName].Add(typeToAdd);

    }

 

    protected override string GetDefaultExtension()

    {

        return (".Designer" + base.GetDefaultExtension());

    }

 

    protected override byte[] GenerateCode(string inputFileContent)

    {

        byte[] generatedCodeAsBytes = null;

 

        try

        {

            XElement csdlContent = ExtractCsdlContent(inputFileContent);

            if (csdlContent == null)

            {

                throw new InvalidOperationException("No CSDL content in input file");

            }

 

            _objectContext = null;

            _entitySetNames = null;

            _typesHierarchyToAddInObjectContext = null;

 

            _first = true;

 

            LanguageOption languageOption = LanguageOption.GenerateCSharpCode;

            string fileExtension = base.GetCodeProvider().FileExtension;

            if (fileExtension != null && fileExtension.Length > 0)

            {

                fileExtension = "." + fileExtension.TrimStart(".".ToCharArray());

            }

            if (fileExtension.EndsWith(".vb", StringComparison.InvariantCultureIgnoreCase))

            {

                languageOption = LanguageOption.GenerateVBCode;

            }

            else if (fileExtension.EndsWith(".cs", StringComparison.InvariantCultureIgnoreCase))

            {

                languageOption = LanguageOption.GenerateCSharpCode;

            }

            else

            {

                throw new InvalidOperationException("Unsupported project language. Only C# and VB are supported.");

            }

 

            if (base.CodeGeneratorProgress != null)

            {

                base.CodeGeneratorProgress.Progress(33, 100);

            }

 

            EntityClassGenerator classGenerator;

            IList<EdmSchemaError> errors = null;

            StringWriter codeWriter = new StringWriter(CultureInfo.InvariantCulture);

            for (int i = 0; i < 2; i++)

            {

                using (XmlReader csdlReader = csdlContent.CreateReader())

                {

                    classGenerator = new EntityClassGenerator(languageOption);

                    classGenerator.OnTypeGenerated += new TypeGeneratedEventHandler(OnTypeGenerated);

                    classGenerator.OnPropertyGenerated += new PropertyGeneratedEventHandler(OnPropertyGenerated);

 

                    errors = classGenerator.GenerateCode(csdlReader, codeWriter);

                }

                if (! _first)

                    break;

                _first = false;

                if (base.CodeGeneratorProgress != null)

                {

                    base.CodeGeneratorProgress.Progress(66, 100);

                }

                codeWriter.Dispose();

                codeWriter = new StringWriter(CultureInfo.InvariantCulture);

            }

            if (errors != null)

            {

                foreach (EdmSchemaError error in errors)

                {

                    int line = (error.Line == 0) ? 0 : (error.Line - 1);

                    int column = (error.Column == 0) ? 0 : (error.Column - 1);

 

                    if (error.Severity == EdmSchemaErrorSeverity.Warning)

                    {

                        base.GeneratorWarning(0, error.Message, (uint)line, (uint)column);

                    }

                    else

                    {

                        base.GeneratorError(4, error.Message, (uint)line, (uint)column);

                    }

                }

            }

 

            generatedCodeAsBytes = Encoding.UTF8.GetBytes(codeWriter.ToString());

            codeWriter.Dispose();

            if (base.CodeGeneratorProgress != null)

            {

                base.CodeGeneratorProgress.Progress(100, 100);

            }

        }

        catch (Exception e)

        {

            base.GeneratorError(4, e.Message, 1, 1);

 

            generatedCodeAsBytes = null;

        }

 

        return generatedCodeAsBytes;

    }

 

    private void OnTypeGenerated(object sender, TypeGeneratedEventArgs eventArgs)

    {

        var entityType = eventArgs.TypeSource as System.Data.Metadata.Edm.EntityType;

        if (_first)

        {

            if (entityType != null)

            {

                EdmType baseType;

                if ((baseType = entityType.BaseType) != null)

                {

                    while (baseType.BaseType != null)

                        baseType = baseType.BaseType;

                    var baseEntityType = baseType as EntityType;

                    AddSubEntities(baseEntityType, entityType);

                }

            }

        }

        else

        {

            eventArgs.AdditionalAttributes.AddRange(CreateCodeAttributes(eventArgs.TypeSource));

            var objectContext = eventArgs.TypeSource as EntityContainer;

            if (objectContext != null)

            {

                _objectContext = objectContext;

                var baseEntitySets = _objectContext.MetadataProperties.FirstOrDefault(mp => mp.Name == "BaseEntitySets");

                if (baseEntitySets != null)

                {

                    foreach (var entitySet in (ReadOnlyMetadataCollection<EntitySetBase>)baseEntitySets.Value)

                    {

                        var derivedBaseEntityType = entitySet.ElementType as EntityType;

                        if (derivedBaseEntityType != null)

                        {

                            EntitySetNames.Add(derivedBaseEntityType.Name, entitySet);

                            if (_typesHierarchyToAddInObjectContext != null)

                                foreach (var derivedEntityType in GetSubEntitiesOf(derivedBaseEntityType))

                                {

                                    string derivedEntityTypeName = derivedEntityType.Name;

                                    var newProp = new CodeMemberProperty { Name = derivedEntityTypeName + "s", Attributes = MemberAttributes.Public | MemberAttributes.Final, Type = new CodeTypeReference("global::System.Linq.IQueryable<" + derivedEntityTypeName + ">") };

                                    newProp.GetStatements.Add(new CodeMethodReturnStatement(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), _entitySetNames[derivedBaseEntityType.Name].Name), "OfType", new CodeTypeReference(derivedEntityTypeName)))));

                                    eventArgs.AdditionalMembers.Add(newProp);

                                }

                        }

                    }

                }

            }

        }

    }

 

    private void OnPropertyGenerated(object sender, PropertyGeneratedEventArgs eventArgs)

    {

        if (!_first)

            eventArgs.AdditionalAttributes.AddRange(CreateCodeAttributes(eventArgs.PropertySource));

    }

 

    private IList<CodeAttributeDeclaration> CreateCodeAttributes(MetadataItem item)

    {

        string xmlns = "http://tempuri.org/AttributeAnnotations";

 

        List<CodeAttributeDeclaration> codeAttributeDeclarations = new List<CodeAttributeDeclaration>();

        if (item != null)

        {

            IEnumerable<MetadataProperty> metadataProperties = item.MetadataProperties.Where(prop => prop.Name.StartsWith(xmlns));

            foreach (MetadataProperty metadataProperty in metadataProperties)

            {

                string metadataPropertyValue = (string)metadataProperty.Value;

                if (!String.IsNullOrEmpty(metadataPropertyValue))

                {

                    string[] attributes = metadataPropertyValue.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string attribute in attributes)

                    {

                        string attributeName = attribute;

                        string[] attributeParams = new string[1];

 

                        if (attribute.Contains('('))

                        {

                            attributeParams = attribute.Split(new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries);

                            attributeName = attributeParams[0];

                        }

 

                        CodeAttributeDeclaration codeAttributeDeclaration = new CodeAttributeDeclaration(attributeName);

 

                        foreach (string attributeParam in attributeParams.Skip(1))

                        {

                            object attributeParamObj = null;

                            bool attributeParamBool = false;

                            if (bool.TryParse(attributeParam, out attributeParamBool))

                            {

                                attributeParamObj = attributeParamBool;

                            }

                            else

                            {

                                attributeParamObj = attributeParam;

                            }

                            codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(attributeParamObj)));

                        }

                        codeAttributeDeclarations.Add(codeAttributeDeclaration);

                    }

                }

            }

        }

        return codeAttributeDeclarations;

    }

 

    private XElement ExtractCsdlContent(string inputFileContent)

    {

        XElement csdlContent = null;

        XNamespace edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx";

        XNamespace edmns = "http://schemas.microsoft.com/ado/2006/04/edm";

 

        XDocument edmxDoc = XDocument.Load(new StringReader(inputFileContent));

        if (edmxDoc != null)

        {

            XElement edmxNode = edmxDoc.Element(edmxns + "Edmx");

            if (edmxNode != null)

            {

                XElement runtimeNode = edmxNode.Element(edmxns + "Runtime");

                if (runtimeNode != null)

                {

                    XElement conceptualModelsNode = runtimeNode.Element(edmxns + "ConceptualModels");

                    if (conceptualModelsNode != null)

                    {

                        csdlContent = conceptualModelsNode.Element(edmns + "Schema");

                    }

                }

            }

        }

        return csdlContent;

    }

}

La grosse différence par rapport à mon précédent générateur custom vient du fait que, le contexte étant généré avant les entity types, je suis obligé ici de faire deux passes de génération. Une pour identifier tous les entity types dérivés et une autre pour réellement générer le code.

Enjoy Big Smile

Posté le jeudi 21 août 2008 13:15 par Matthieu MEZIL | 1 commentaire(s)

Debugguer avec le code du framework

Avec VS 2008, il était devenu possible de débuguer avec le code du framework, cependant, on n'avait pas accès à toutes les dlls.

Avec le SP1 si. De plus, l'activation de cette fonctionnalité a été grandement simplifié : plus besoin de ne rien faire, il suffit de faire un click droit sur la CallStack sur une instruction à laquelle vous n'avez pas accès par défaut suivi d'un "Load Symbols From" suivi d'un "Microsoft Symbol Servers".

Posté le jeudi 21 août 2008 01:21 par Matthieu MEZIL | 1 commentaire(s)

Classé sous : ,

EF : SELECT avec une SP

Imaginons que l'on ait une procédure stockée qui fait juste un SELECT et que l'on veuille l'intégrer dans notre EDM. Pour cela, on va créer un entity type. Maintenant imposons une contrainte : il faut forcément passer par la procédure stockée pour récupérer des instances de notre entity type.

Ce scenario n'est pas vraiment prévu par la V1 de l'Entity Framework et de "bidouiller" un peu.

Imaginons la procédure stockée suivante :

CREATE PROCEDURE [dbo].[TOTO]

AS

BEGIN

      SELECT Id, 1 AS VALUE FROM Test

END

Quand on va récupérer notre procédure stockée dans notre EDM, on va avoir l'info dans le SSDL :

<Function Name="TOTO" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" />

Ensuite, il faut créer l'entity type dans le CSDL (ce que l'on peut faire avec le designer) :

<EntityType Name="Toto">

    <Key>

        <PropertyRef Name="Id" />

    </Key>

    <Property Name="Id" Type="Int32" Nullable="false" />

    <Property Name="Value" Type="Int32" Nullable="true" />

</EntityType>

Avec la création de l'entity type, on va également avoir un EntitySet :

<EntitySet Name="Totos" EntityType="TestModel.Toto" />

Ensuite, on peut ajouter un "Function Import" dans le CSDL, ce qui aura pour effet de rajouter une méthode à notre ObjectContext qui appellera notre procédure stockée. Il suffit de lui préciser lors de l'ajout de "Function Import" que le type de retour est Toto.

Attention, il faut que les propriétés de Toto aient le même nom que les colonnes retournées par notre procédure stockée.

Ensuite, comme on ne veut pas de l'EntitySet sur notre ObjectContext (car on veut obligatoirement passé via la proc stock), on va le passer en private. A noter, qu'il est possible de le faire via le designer en allant sur la fenêtre Model Browser, puis en déroulant le noeud EntityContainer puis EntitySets puis en allant sur le noeud Totos, en allant ensuite dans la fenêtre Properties et en modifiant la visibilité du Getter.

On pourrait penser que c'est bon mais non ! En effet, il faut obligatoirement mapper l'entity type Toto (même si dans notre cas ça ne sert à rien).

Comment faire ?

Avec une SSDL View.

Vu qu'elle ne sera pas utilisée, on peut mettre n'importe quoi comme commande SQL :

<EntityContainer Name="TestModelStoreContainer">

    <EntitySet Name="Totos" EntityType="TestModel.Store.Toto" >

        <DefiningQuery>

            SELECT 1

        </DefiningQuery>

    </EntitySet>

</EntityContainer>

<EntityType Name="Toto">

    <Key>

        <PropertyRef Name="Id"/>

    </Key>

    <Property Name="Id" Type="int" Nullable="false"/>

    <Property Name="Value" Type="int" />

</EntityType>

et ensuite mapper l'entity type sur notre SSDL View.

Pour la V2, ce scenario sera réellement supporté comme me l'a confirmé Daniel Simmons : "In v2 we’re allowing sprocs to return complex types which will be the easy way to return objects which don’t need to be mapped, etc."

Posté le mercredi 20 août 2008 13:19 par Matthieu MEZIL | 2 commentaire(s)

Classé sous : , , ,

SSDL View et CUD operations

Je viens de recevoir un mail dont voici un extrait sur lequel j'ai décidé de bloggué :

"Pour ce qui est de mon problème, effectivement avec une SSDL View ça marche très bien. Même les mises à jour fonctionnent, par contre impossible de faire des insert dans une des tables.

Je pensais que les SSDL View n'avaient pas la possibilité d'être updaté, alors pourquoi l'update marche et non l'insert, y a-t-il une solution pour que l'insert puisse marcher avec cette SSDL View ?"

Tout d'abord pourquoi l'insert ne marche pas ?

C'est normal, on a réalisé de l'Entity Splitting et on a inclut le résultat d'une vue dans le mapping de l'entité. Or les vues sont censé être Read Only (oui je sais Christian, dans SQL Server, il est possible d'utiliser des triggers sur une vue) donc ne supportent pas l'INSERT.

Pourquoi l'update marche ?

En fait l'update ne marche que partiellement. Dans le cas de l'EntitySplitting, l'update exécuté en base ne prendra pas systèmatiquement toutes les tables mappées avec l'entité mais seulement celles qui ont réellement été modifiées. Dans le cas présent, si on ne modifie que les propriétés mappées sur la vraie table, l'update généré ne va impacter que la table et pas la SSDL View. Voilà pourquoi ça marche. Par contre, si on modifie une des propriétés mappées sur une colonne de la SSDL View, l'update ne fonctionnera pas.

Comment faire en sorte que l'INSERT / UPDATE / DELETE fonctionnent sans problème ?

Pour cela, il faut utiliser des SSDL Functions (qui pourront appeler des procédures stockées).

Prenons un exemple.

Voici mon SSDL avant l'ajout des SSDL Functions :

<EntityContainer Name="TestModelStoreContainer">

    <EntitySet Name="Test" EntityType="TestModel.Store.Test" store:Type="Tables" Schema="dbo" />

    <EntitySet Name="StupidTest" EntityType="TestModel.Store.StupidTest" >

        <DefiningQuery>

            SELECT Id, 1 AS VALUE

            FROM Test

        </DefiningQuery>

    </EntitySet>

</EntityContainer>

<EntityType Name="Test">

    <Key>

        <PropertyRef Name="Id" />

    </Key>

    <Property Name="Id" Type="int" Nullable="false" />

    <Property Name="Name" Type="nvarchar" MaxLength="50" />

</EntityType>

<EntityType Name="StupidTest">

    <Key>

        <PropertyRef Name="Id" />

    </Key>

    <Property Name="Id" Type="int" Nullable="false" />

    <Property Name="Value" Type="int" />

</EntityType>

L'entité Test a trois propriétés :

  • Id (Key)
  • Name
  • Value (RO)

Il est important de noter que si on utilise une SSDL Function pour une des trois opérations de type CUD, il faudra utiliser des SSDL Functions pour les trois (du moins dans la v1 de l'EF).

On va donc définir trois SSDL Functions :

<Function Name="InsertTest" IsComposable="false">

    <

CommandText>INSERT INTO Test VALUES(@Id, @Name)</CommandText>

    <Parameter Name="Id" Type="int" Mode="In" />

    <Parameter Name="Name" Type="nvarchar" Mode="In" />

</Function>

<Function Name="UpdateTest" IsComposable="false">

    <CommandText>UPDATE Test SET Name = @Name WHERE Id = @Id</CommandText>

    <Parameter Name="Id" Type="int" Mode="In" />

    <Parameter Name="Name" Type="nvarchar" Mode="In" />

</Function>

<Function Name="DeleteTest" IsComposable="false">

    <</