【问题标题】:Trying to serialize [NotMapped] Entity Framework property for use by Breeze尝试序列化 [NotMapped] 实体框架属性以供 Breeze 使用
【发布时间】:2015-08-19 20:45:37
【问题描述】:

这是一个有点复杂的问题,请耐心等待。我们目前在我们的服务器上使用 Entity Framework 6.1.1 和 OData 5.6,在客户端使用 Breeze JS 1.5.4。简而言之,我们在获取模型上的 [NotMapped] 属性以序列化为 json 并传递给客户端时遇到问题。

这是我们的模型:

public class Request 
{
    ...
    public int UserId { get; set; }

    [NotMapped]
    public string UserName {get; set; }
}

因为我们使用的是 OData,而不是通过默认的 JsonMediaTypeFormatter 进行序列化,所以它通过 OdataMediaTypeFormatter 完全忽略具有 [NotMapped] 属性的任何内容。 我们可以通过手动将属性添加到modelBuilder 来解决这个问题。然而,当尝试与 Breeze 集成时,这会成为一个问题,因为它们有自己的自定义 EdmBuilder,必须用于保留可导航属性等内容,而我们不能使用标准 ODataConventionModelBuilder。这个自定义构建器似乎不允许对模型进行任何级别的控制。是否有可能强制 OData 正确序列化这些属性并保留与 Breeze 相关的元数据?有没有人尝试过类似的东西?

旁注: 我们正在尝试避免在数据库中存储或仅在数据库中为这些数据创建虚拟列,因为我们需要其中的 5 个属性,但如果我们在这方面投入太多时间,这可能会成为我们的行动方针。

提前致谢

【问题讨论】:

    标签: c# json entity-framework odata breeze


    【解决方案1】:

    在序列化方面,伤害你的是由微风提供的中间 EdmBuilder。见:https://github.com/Breeze/breeze.server.labs/blob/master/EdmBuilder.cs

    由于 EdmBuilder.cs 的 cmets 中定义的限制

    我们需要 EDM 来定义 Web API OData 路由并作为 Breeze 客户端的元数据源。 Web API OData 文献推荐 System.Web.Http.OData.Builder.ODataConventionModelBuilder。 该组件足以定义路由,但无法作为 Breeze 的元数据源,因为(在撰写本文时)它忽略了包含 Breeze 维护客户端 JavaScript 实体的导航属性所需的外键定义。 此 EDM 生成器要求 EF DbContext 提供满足路由定义和 Breeze 的元数据。 您只会获得 EntityFramework 选择公开的元数据。这可以防止 OData 格式化程序/序列化程序包含该属性 - 它未映射到模型元数据中。

    您可以尝试使用自定义序列化程序的解决方案,类似于本文中介绍的内容。 Using OData in webapi for properties known only at runtime

    自定义序列化程序大致如下所示(注意:这不起作用..继续阅读,下面...)

    public class CustomEntitySerializer : ODataEntityTypeSerializer
    {
        public CustomEntitySerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider) {    }
    
        public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
        {
            ODataEntry entry = base.CreateEntry(selectExpandNode, entityInstanceContext);           
    
            Request item = entityInstanceContext.EntityInstance as Request;
            if (entry != null && item != null)
            {
                // add your "NotMapped" property here.
                entry.Properties = new List<ODataProperty>(entry.Properties) { new ODataProperty { Name = "UserName", Value = item.UserName} };
            }
            return entry;
        }
    }
    

    问题在于底层 ODataJsonLightPropertySerializer 在尝试写入时会检查模型是否存在属性。它调用 Microsoft.Data.OData.WriterValidationUtils 类中的 ValidatePropertyDefined 方法。

    internal static IEdmProperty ValidatePropertyDefined(string propertyName, IEdmStructuredType owningStructuredType)
    

    这将使您因运行时异常而失败:

    类型“YourNamespace.Models.Request”上不存在属性“用户名” .确保仅使用由类型定义的属性名称。","type":"Microsoft.Data.OData.ODataException" "stacktrace":" 在 Microsoft.Data.OData.WriterValidationUtils.ValidatePropertyDefined (String propertyName , IEdmStructuredType owningStructuredType)\r\n 在 Microsoft.Data.OData.JsonLight.ODataJsonLightPropertySerializer .WriteProperty(ODataProperty 属性, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty , DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectionProperties

    底线是需要在模型中定义属性才能对其进行序列化。可以想象,您可以重写序列化层的大部分内容,但是 OData 框架中有很多内部/静态/私有/非虚拟位,这让人不快。

    解决方案最终以 Breeze 强制您生成模型的方式呈现。假设代码优先实现,您可以将其他模型元数据直接注入到 EntityFramework 生成的 XmlDocument 中。采用 Breeze EdmBuilder 中的方法,稍作修改:

    static IEdmModel GetCodeFirstEdm<T>(this T dbContext)  where T : DbContext
    {
        // create the XmlDoc from the EF metadata
        XmlDocument metadataDocument = new XmlDocument();
        using (var stream = new MemoryStream())
        using (var writer = XmlWriter.Create(stream))
        {
            System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(dbContext, writer);
            stream.Position = 0;
            metadataDocument.Load(stream);
        }
    
        // to support proper xpath queries
        var nsm = new XmlNamespaceManager(metadataDocument.NameTable);
        nsm.AddNamespace("ssdl", "http://schemas.microsoft.com/ado/2009/02/edm/ssdl");
        nsm.AddNamespace("edmx", "http://schemas.microsoft.com/ado/2009/11/edmx");
        nsm.AddNamespace("edm", "http://schemas.microsoft.com/ado/2009/11/edm");
    
        // find the node we want to work with & add the 1..N property metadata
        var typeElement = metadataDocument.SelectSingleNode("//edmx:Edmx/edmx:Runtime/edmx:ConceptualModels/edm:Schema/edm:EntityType[@Name=\"Request\"]", nsm);
    
        // effectively, we want to insert this.
        // <Property Name="UserName" Type="String" MaxLength="1000" FixedLength="false" Unicode="true" Nullable="true" />
        var propElement = metadataDocument.CreateElement(null, "Property", "http://schemas.microsoft.com/ado/2009/11/edm");
        propElement.SetAttribute("Name", "UserName");
        propElement.SetAttribute("Type", "String");
        propElement.SetAttribute("FixedLength", "false");
        propElement.SetAttribute("Unicode", "true");
        propElement.SetAttribute("Nullable", "true");
    
        // append the node to the type element
        typeElement.AppendChild(propElement);
    
        // now we're going to save the updated xml doc and parse it.
        using (var stream = new MemoryStream())
        {
            metadataDocument.Save(stream);
            stream.Position = 0;
            using (var reader = XmlReader.Create(stream))
            {
                return EdmxReader.Parse(reader);
            }
        }
    }
    

    这会将属性放入要由 OData 层使用的元数据中,并且不需要任何额外的步骤来促进序列化。但是,您需要注意如何塑造模型元数据,因为任何需求/规范都将反映在 Breeze 的客户端验证中。

    我已经在 Breeze 提供的 ODataBreezejs 示例中验证了这种方法的 CRUD 操作。 https://github.com/Breeze/breeze.js.samples/tree/master/net/ODataBreezejsSample

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-28
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多