【问题标题】:EntityFramework Model-First metadata for breezejsEntityFramework Model-First 元数据,用于微风js
【发布时间】:2014-03-28 11:23:29
【问题描述】:

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

创建 DbModelBuilder 或从创建的 DbContext 编写 EDMX 不支持使用 Database First 或 Model First。 EDMX 只能是 从 Code First DbContext 中获得,而无需使用现有的 DbCompiledModel。

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

【问题讨论】:

    标签: entity-framework breeze asp.net-web-api-odata


    【解决方案1】:

    由于生成此信息将在运行时完成,因此它必须与读取加载的资源有关。当我试图弄清楚时,我找到了这个链接; 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";
        }
    }
    

    【讨论】:

    • 这看起来很有希望。我可以将它包含在微风实验室版本的更新中,以便每个人都能找到它吗? 当然,我会明确地感谢你。 P.S.:对于向后兼容,我可能会将 GetCodeFirstEdm 别名为已弃用的 GetEdm
    • @Ward 当然,那太好了。关于 GetEdm 方法,在理想情况下,我们应该能够理解 DbContext 类型(无论是 Code-First 还是 Model-First)。在这种情况下,GetEdm 方法可以保留,它可以在内部调用 GetCodeFirstEdm 或 GetModelFirstEdm。由于您正在谈论添加它轻而易举,我将尝试对此进行调查。如果我能找到办法,我会更新我的答案并让你知道?
    • 如果我们能分辨出 DbContext 是先构建代码还是先构建数据库,那就太棒了。当我看到还需要多少 using 语句时,我有些不情愿。但是它们似乎都在您无论如何都会引用的程序集中。
    • 我已提交更新(带有轻微修改),感谢您,并将 nuget 包更新到 v.1.0.3。感谢您的贡献。
    • 谢谢,我将更新实验室及其 nuget。我将恢复为一种方法并将其他方法设为私有。这才两天,所以我想我不会担心会破坏任何人。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多