试图序列化[NotMapped]实体框架属性以供Breeze使用

本文关键字:属性 Breeze 使用 框架 实体 序列化 NotMapped | 更新日期: 2023-09-27 18:09:00

这是一个有点复杂的问题,所以请耐心听我说。我们目前在服务器端使用的是实体框架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个这些属性,但如果我们在这方面投入太多时间,这可能会成为我们的行动方针。

Thanks in Advance

试图序列化[NotMapped]实体框架属性以供Breeze使用

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

由于EdmBuilder.cs

注释中定义的限制<>之前我们需要EDM来定义Web API OData路由,并将其作为Breeze客户端的元数据源。Web API OData文献推荐使用system .Web. http .OData. builder . odatacontiononmodelbuilder。该组件对于路由定义来说是足够的,但是作为Breeze的元数据来源是失败的,因为(在撰写本文时)它忽略了包括维护客户端JavaScript实体的导航属性所需的外键定义。这个EDM Builder要求EF DbContext提供同时满足路由定义和Breeze的元数据。您只获得了EntityFramework选择公开的元数据。这可以防止OData格式化器/序列化器包含该属性——它没有映射到模型元数据中。之前

您可以尝试使用自定义序列化器的解决方案,类似于本文中所表示的。在webapi中使用OData来获取只在运行时才知道的属性

一个自定义序列化器看起来大概像这样(注意:这不起作用…

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)

这会导致运行时异常:

<>之前属性'UserName'在类型' yournamespaces . models . request '上不存在. 确保只使用由类型定义的属性名。","type":"Microsoft.Data.OData.ODataException","stacktrace":" at Microsoft.Data.OData.WriterValidationUtils. "ValidatePropertyDefined (String propertyName'r'n在Microsoft.Data.OData.JsonLight.ODataJsonLightPropertySerializer.WriteProperty(ODataProperty属性,IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty、DuplicatePropertyNamesChecker、projectedpropertiesannotationprojectedproperties之前

底线是属性需要在模型中定义,以便序列化它。可以想象,您可以重写序列化层的大部分内容,但是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