EntityFramework Model-First метаданные для breezejs

Библиотеке Breeze.js требуются метаданные контекста сущности. OData веб-API имеет ODataConventionModelBuilder по умолчанию для этой операции, но не работает для Breeze, так как в нем отсутствует информация о внешнем ключе. Поэтому Breeze предлагает специальный пакет EdmBuilder для генерации этой информации. Однако он работает только с подходом Code-First. Если существует существующий файл edmx, это дает следующее исключение;

Создание DbModelBuilder или запись EDMX из DbContext, созданного с использованием Database First или Model First, не поддерживается. EDMX можно получить только из DbContext Code First, созданного без использования существующей модели DbCompiledModel.

Короче говоря, если в проекте есть существующий файл edmx, как его можно опубликовать в качестве метаданных для breezejs?


person coni2k    schedule 28.03.2014    source источник


Ответы (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";
    }
}
person coni2k    schedule 28.03.2014
comment
Это выглядит ОЧЕНЬ ПЕРСПЕКТИВНЫМ. Могу ли я включить его в обновление версии breeze labs, чтобы все могли его найти? Конечно, я буду вам искренне благодарен. PS: для обратной совместимости я бы, скорее всего, использовал псевдоним GetCodeFirstEdm с устаревшим GetEdm. - person Ward; 28.03.2014
comment
@Ward Конечно, это было бы здорово. Что касается метода GetEdm, в идеальном случае мы должны понимать тип DbContext (будь то Code-First или Model-First). В этом случае метод GetEdm может остаться и внутренне вызвать либо GetCodeFirstEdm, либо GetModelFirstEdm. Поскольку вы говорите о добавлении бриза, я попытаюсь исследовать это. Если я найду способ, я обновлю свой ответ и дам вам знать? - person coni2k; 30.03.2014
comment
Было бы замечательно, если бы мы могли определить, был ли DbContext первым код сборки или db. У меня был момент нежелания, когда я увидел, сколько еще операторов использования требуется. Но все они, кажется, находятся в сборках, на которые вы бы ссылались в любом случае. - person Ward; 30.03.2014
comment
Я зафиксировал обновление (с небольшими изменениями), упомянул вас и обновил пакет nuget до версии 1.0.3. Спасибо за ваш вклад. - person Ward; 30.03.2014
comment
@Ward Спасибо, я видел новую версию. И я работал над пониманием типа контекста. Поскольку я не смог найти никакой документации по этому поводу, я просто сравнил оба типа контекста. Есть свойство CodeFirstModel; в типе Code-First он имеет значение, а в Model-First — нуль. Новый метод (GetEdmModel) вызывает один из внутренних методов на основе этого результата. В настоящее время это выглядит хорошо, должен ли я обновить ответ или отправить его вам напрямую? - person coni2k; 31.03.2014
comment
@Ward Я обновил ответ. Хотя они используются внутренне, я сделал старые методы устаревшими. Вы можете проверить комментарии и замечания. - person coni2k; 31.03.2014
comment
Спасибо, я обновлю лабораторию и ее nuget. Я вернусь к одному методу и сделаю остальные закрытыми. Прошло всего два дня, так что я не думаю, что буду беспокоиться о том, чтобы кого-то сломать. - person Ward; 31.03.2014
comment
Сделанный. Нужет v.1.0.4. Теперь мы вернулись к одному методу... GetEdm... оригинальное имя, расширенное до рабочего кода или модели. Я исключил из списка v.1.0.3. Я проверил модель с первым кодом. Пожалуйста, подтвердите, что первая версия модели работает. p.s.: GetEdmModel является избыточным, так как «M» в EDM — это модель :-) - person Ward; 31.03.2014
comment
Да, видел, теперь выглядит очень чистым. И протестировал его с помощью Model-First, он работает. Хорошая работа, сэр! :) Кстати, я разместил еще одно небольшое обновление о бризе в его UserVoice. Если вам интересно; Функция createError breeze.js не может обрабатывать ошибки datajs - person coni2k; 31.03.2014
comment
@Ward Не могли бы вы обновить документ breezejs.com/samples/breeze-web-api -одата - person Patrick J Collins; 03.04.2014
comment
@Ward Также ссылки на nuget и github заканчиваются символом запятой и не работают. - person coni2k; 03.04.2014
comment
Патрик обновляется для следующего релиза (примерно через неделю). Тем временем вы можете обновить пакет nuget самостоятельно. - person Ward; 04.04.2014
comment
Серкан спасибо, что нашел это; Кажется, я исправил эти ссылки. - person Ward; 04.04.2014
comment
Извините, что захватил этот вопрос, есть ли у кого-нибудь способ установить набор сущностей на сгенерированные базы данных edms, настроенные так? stackoverflow.com/ вопросы/22852292/ - person Chi Row; 04.04.2014