1

Breeze.js ライブラリには、エンティティ コンテキストのメタデータが必要です。Web API OData には、この操作用のデフォルトの ODataConventionModelBuilder がありますが、外部キー情報がないため、Breeze では機能しません。したがって、Breeze は、この情報を生成するために「EdmBuilder」と呼ばれる特別なパッケージを提供しています。ただし、Code-First アプローチでのみ機能します。既存の edmx ファイルがある場合、次の例外が発生します。

DbModelBuilder の作成、または Database First または Model First を使用して作成された DbContext からの EDMX の書き込みはサポートされていません。EDMX は、既存の DbCompiledModel を使用せずに作成された Code First DbContext からのみ取得できます。

要するに、プロジェクト内に既存のedmxファイルが存在する場合、それをメタデータ情報としてbreezejsに公開するにはどうすればよいのでしょうか?

4

1 に答える 1

3

この情報の生成は実行時に行われるため、ロードされたリソースを読み取る必要がありました。理解しようとしているときに、このリンクを見つけました。 https://gist.github.com/dariusclay/8673940

唯一の問題は、接続文字列に対して正規表現パターンが機能しなかったことです。しかし、それを修正した後、そよ風が探していた情報が生成されました。

最終的に、breeze の Code-First メソッドとこの Model-First メソッドの両方を次のクラスにマージしました (確実に改善される可能性があります)。それが他の誰かに役立つことを願っています。

アップデート

また、DBContext が Code-First か Model-First かを判断するようになりました。

using Microsoft.Data.Edm.Csdl;
using Microsoft.Data.Edm.Validation;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Infrastructure;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;

namespace Microsoft.Data.Edm
{
    /// <summary>
    /// DbContext extension that builds an "Entity Data Model" (EDM) from a <see cref="DbContext"/>
    /// </summary>
    /// <remarks>
    /// We need the EDM both to define the Web API OData route and as a
    /// source of metadata for the Breeze client. 
    /// <p>
    /// The Web API OData literature recommends the
    /// <see cref="System.Web.Http.OData.Builder.ODataConventionModelBuilder"/>.
    /// That component is suffient for route definition but fails as a source of 
    /// metadata for Breeze because (as of this writing) it neglects to include the
    /// foreign key definitions Breeze requires to maintain navigation properties
    /// of client-side JavaScript entities.
    /// </p><p>
    /// This EDM Builder ask the EF DbContext to supply the metadata which 
    /// satisfy both route definition and Breeze.
    /// </p><p>
    /// This class can be downloaded and installed as a nuget package:
    /// http://www.nuget.org/packages/Breeze.EdmBuilder/
    /// </p>
    /// </remarks>
    public static class EdmBuilder
    {
        /// <summary>
        /// Builds an Entity Data Model (EDM) from a <see cref="DbContext"/>.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <example>
        /// <![CDATA[
        /// /* In the WebApiConfig.cs */
        /// config.Routes.MapODataRoute(
        ///     routeName: "odata", 
        ///     routePrefix: "odata", 
        ///     model: EdmBuilder.GetEdmModel<DbContext>(), 
        ///     batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///     );
        /// ]]>
        /// </example>
        public static IEdmModel GetEdmModel<T>() where T : DbContext, new()
        {
            return GetEdmModel<T>(new T());
        }

        /// <summary>
        /// Extension method builds an Entity Data Model (EDM) from an
        /// existing <see cref="DbContext"/>.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <param name="dbContext">Concrete <see cref="DbContext"/> to use for EDM generation.</param>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <example>
        /// /* In the WebApiConfig.cs */
        /// using (var context = new TodoListContext())
        /// {
        ///   config.Routes.MapODataRoute(
        ///       routeName: "odata", 
        ///       routePrefix: "odata", 
        ///       model: context.GetEdmModel(), 
        ///       batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///       );
        /// }
        /// </example>
        public static IEdmModel GetEdmModel<T>(this T dbContext) where T : DbContext, new()
        {
            dbContext = dbContext ?? new T();

            // Get internal context
            var internalContext = dbContext.GetType().GetProperty(INTERNALCONTEXT, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(dbContext);

            // Is code first model?
            var isCodeFirst = internalContext.GetType().GetProperty(CODEFIRSTMODEL).GetValue(internalContext) != null;

            // Return the result based on the dbcontext type
            return isCodeFirst
                ? GetCodeFirstEdm<T>(dbContext)
                : GetModelFirstEdm<T>(dbContext);
        }


        /// <summary>
        /// [OBSOLETE] Builds an Entity Data Model (EDM) from an existing <see cref="DbContext"/> 
        /// created using Code-First. Use <see cref="GetCodeFirstEdm"/> instead.
        /// </summary>
        /// <remarks>
        /// This method delegates directly to <see cref="GetCodeFirstEdm"/> whose
        /// name better describes its purpose and specificity.
        /// Deprecated for backward compatibility.
        /// </remarks>
        [Obsolete("This method is obsolete. Use GetEdmModel instead")]
        public static IEdmModel GetEdm<T>(this T dbContext) where T : DbContext, new()
        {
            return GetEdmModel<T>(dbContext);
        }

        /// <summary>
        /// [OBSOLETE] Builds an Entity Data Model (EDM) from a <see cref="DbContext"/> created using Code-First.
        /// Use <see cref="GetModelFirstEdm"/> for a Model-First DbContext.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <example>
        /// <![CDATA[
        /// /* In the WebApiConfig.cs */
        /// config.Routes.MapODataRoute(
        ///     routeName: "odata", 
        ///     routePrefix: "odata", 
        ///     model: EdmBuilder.GetCodeFirstEdm<CodeFirstDbContext>(), 
        ///     batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///     );
        /// ]]>
        /// </example>
        [Obsolete("This method is obsolete. Use GetEdmModel instead")]
        public static IEdmModel GetCodeFirstEdm<T>() where T : DbContext, new()
        {
            return GetCodeFirstEdm(new T());
        }

        /// <summary>
        /// [OBSOLETE] Extension method builds an Entity Data Model (EDM) from an
        /// existing <see cref="DbContext"/> created using Code-First.
        /// Use <see cref="GetModelFirstEdm"/> for a Model-First DbContext.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <param name="dbContext">Concrete <see cref="DbContext"/> to use for EDM generation.</param>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <example>
        /// /* In the WebApiConfig.cs */
        /// using (var context = new TodoListContext())
        /// {
        ///   config.Routes.MapODataRoute(
        ///       routeName: "odata", 
        ///       routePrefix: "odata", 
        ///       model: context.GetCodeFirstEdm(), 
        ///       batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///       );
        /// }
        /// </example>
        [Obsolete("This method is obsolete. Use GetEdmModel instead")]
        public static IEdmModel GetCodeFirstEdm<T>(this T dbContext) where T : DbContext, new()
        {
            using (var stream = new MemoryStream())
            {
                using (var writer = XmlWriter.Create(stream))
                {
                    dbContext = dbContext ?? new T();
                    System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(dbContext, writer);
                }
                stream.Position = 0;
                using (var reader = XmlReader.Create(stream))
                {
                    return EdmxReader.Parse(reader);
                }
            }
        }

        /// <summary>
        /// [OBSOLETE] Builds an Entity Data Model (EDM) from a <see cref="DbContext"/> created using Model-First.
        /// Use <see cref="GetCodeFirstEdm"/> for a Code-First DbContext.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <example>
        /// <![CDATA[
        /// /* In the WebApiConfig.cs */
        /// config.Routes.MapODataRoute(
        ///     routeName: "odata", 
        ///     routePrefix: "odata", 
        ///     model: EdmBuilder.GetModelFirstEdm<ModelFirstDbContext>(), 
        ///     batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///     );
        /// ]]>
        /// </example>
        [Obsolete("This method is obsolete. Use GetEdmModel instead")]
        public static IEdmModel GetModelFirstEdm<T>() where T : DbContext, new()
        {
            return GetModelFirstEdm(new T());
        }

        /// <summary>
        /// [OBSOLETE] Extension method builds an Entity Data Model (EDM) from a <see cref="DbContext"/> created using Model-First. 
        /// Use <see cref="GetCodeFirstEdm"/> for a Code-First DbContext.
        /// </summary>
        /// <typeparam name="T">Type of the source <see cref="DbContext"/></typeparam>
        /// <param name="dbContext">Concrete <see cref="DbContext"/> to use for EDM generation.</param>
        /// <returns>An XML <see cref="IEdmModel"/>.</returns>
        /// <remarks>
        /// Inspiration and code for this method came from the following gist
        /// which reates the metadata from an Edmx file:
        /// https://gist.github.com/dariusclay/8673940
        /// </remarks>
        /// <example>
        /// /* In the WebApiConfig.cs */
        /// using (var context = new TodoListContext())
        /// {
        ///   config.Routes.MapODataRoute(
        ///       routeName: "odata", 
        ///       routePrefix: "odata", 
        ///       model: context.GetModelFirstEdm(), 
        ///       batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)
        ///       );
        /// }
        /// </example> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Usage", "CA2202:Do not dispose objects multiple times" )]
        [Obsolete("This method is obsolete. Use GetEdmModel instead")]
        public static IEdmModel GetModelFirstEdm<T>(this T dbContext) where T : DbContext, new()
        {
            dbContext = dbContext ?? new T();
            using (var csdlStream = GetCsdlResourceStream(dbContext))
            {
                using (var reader = XmlReader.Create(csdlStream))
                {
                    IEdmModel model;
                    IEnumerable<EdmError> errors;
                    if (!CsdlReader.TryParse(new[] { reader }, out model, out errors))
                    {
                        foreach (var e in errors)
                            Debug.Fail(e.ErrorCode.ToString("F"), e.ErrorMessage);
                    }
                    return model;
                }
            }
        }

        static Stream GetCsdlResourceStream(IObjectContextAdapter context)
        {
            // Get connection string builder
            var connectionStringBuilder = new EntityConnectionStringBuilder(context.ObjectContext.Connection.ConnectionString);

            // Get the regex match from metadata property of the builder
            var match = Regex.Match(connectionStringBuilder.Metadata, METADATACSDLPATTERN);

            // Get the resource name
            var resourceName = match.Groups[0].Value;

            // Get context assembly
            var assembly = Assembly.GetAssembly(context.GetType());

            // Return the csdl resource
            return assembly.GetManifestResourceStream(resourceName);
        }

        // Pattern to find conceptual model name in connecting string metadata
        const string METADATACSDLPATTERN = "((\\w+\\.)+csdl)";

        // Property name in DbContext class
        const string INTERNALCONTEXT = "InternalContext";

        // Property name in InternalContext class
        const string CODEFIRSTMODEL = "CodeFirstModel";
    }
}
于 2014-03-28T11:23:29.320 に答える