1

Breeze.js 库需要实体上下文的元数据。Web API OData 有一个用于此操作的默认 ODataConventionModelBuilder,但不适用于 Breeze,因为它缺少外键信息。因此,Breeze 提供了一个名为“EdmBuilder”的特殊包来生成此信息。但是,它仅适用于 Code-First 方法。如果存在现有的 edmx 文件,则会给出以下异常;

不支持从使用 Database First 或 Model First 创建的 DbContext 创建 DbModelBuilder 或编写 EDMX。EDMX 只能从不使用现有 DbCompiledModel 创建的 Code First DbContext 中获取。

简而言之,如果项目中有一个现有的edmx文件,如何将它作为元数据信息发布到breezejs?

4

1 回答 1

3

由于生成此信息将在运行时完成,因此它必须与读取加载的资源有关。当我试图弄清楚时,我找到了这个链接; https://gist.github.com/dariusclay/8673940

唯一的问题是正则表达式模式不适用于我的连接字符串。但是在修复之后,它生成了微风正在寻找的信息。

最后,我在下面的类中合并了轻量级的 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 回答