简体   繁体   English

尝试序列化[NotMapped] Entity Framework属性以供Breeze使用

[英]Trying to serialize [NotMapped] Entity Framework property for use by Breeze

This is a bit of a convoluted problem, so bear with me. 这是一个令人费解的问题,所以请耐心等待。 We are currently using Entity Framework 6.1.1 on our server with OData 5.6, and Breeze JS 1.5.4 on the client side. 我们目前在我们的服务器上使用Entity Framework 6.1.1和OData 5.6,以及客户端的Breeze JS 1.5.4。 In short, we're having issues getting [NotMapped] properties on our Model to serialize into json and get passed to the client. 简而言之,我们在模型上获取[NotMapped]属性以将其序列化为json并传递给客户端时遇到问题。

Here is our Model: 这是我们的模型:

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

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

Because we're using OData, rather than being serialized through the default JsonMediaTypeFormatter , it goes through the OdataMediaTypeFormatter which completely ignores anything with the [NotMapped] attribute. 因为我们使用的是OData,而不是通过默认的JsonMediaTypeFormatter进行序列化,所以它会通过OdataMediaTypeFormatter完全忽略具有[NotMapped]属性的任何内容。 We could work around this problem by manually adding the properties to the modelBuilder . 我们可以通过手动将属性添加到modelBuilder来解决此问题。 However this becomes an issue when trying to integrate with Breeze, because they have their own custom EdmBuilder that must be used for things like navigable properties to be preserved, and we cannot use the standard ODataConventionModelBuilder . 然而,当尝试与Breeze集成时,这成为一个问题,因为它们有自己的自定义EdmBuilder,必须用于保存可导航属性之类的东西,并且我们不能使用标准的ODataConventionModelBuilder This custom builder doesn't seem to allow for any level of control over the models. 此自定义构建器似乎不允许对模型进行任何级别的控制。 Is it at all possible to force OData to properly serialize these properties and also keep metadata that's complaint with Breeze? 是否可以强制OData正确序列化这些属性,并保留与Breeze一起投诉的元数据? Has anyone tried something similar before? 有没有人尝试过类似的东西?

Side note: We're trying to avoid storing or just making dummy columns in the db for this data, seeing as we need 5 of these properties, but this may wind up being our course of action if we dump too much more time into this. 旁注:我们试图避免在数据库中存储或只是为这些数据创建虚拟列,因为我们需要其中的5个属性,但如果我们将更多的时间用于此,这可能会成为我们的行动方式。 。

Thanks in Advance 提前致谢

In terms of serialization, what is hurting you is the intermediate EdmBuilder that is supplied by breeze. 在序列化方面,令你害怕的是由微风提供的中间EdmBuilder。 See: https://github.com/Breeze/breeze.server.labs/blob/master/EdmBuilder.cs 请参阅: https//github.com/Breeze/breeze.server.labs/blob/master/EdmBuilder.cs

Because of the limitations defined in the comments of the EdmBuilder.cs 由于EdmBuilder.cs的注释中定义的限制

We need the EDM both to define the Web API OData route and as a source of metadata for the Breeze client.  The Web API OData literature recommends the 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.
This EDM Builder ask the EF DbContext to supply the metadata which satisfy both route definition and Breeze.
You're only getting the metadata the EntityFramework chooses to expose.  This prevents the OData formatters/serializers from including the property - it's not mapped in the model metadata.

You could attempt a solution with a custom serializer, similar to what is represented in this article. 您可以尝试使用自定义序列化程序的解决方案,类似于本文中所表示的。 Using OData in webapi for properties known only at runtime 在webapi中使用OData来获取仅在运行时知道的属性

A custom serializer would look roughly like this (Note: this DOES NOT work.. continue reading, below...) 自定义序列化程序看起来大致如此(注意:这不起作用..继续阅读,下面......)

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;
    }
}

The trouble with this is that the underlying ODataJsonLightPropertySerializer checks the model for the existence of the property as it's attempting to write. 这样做的问题在于底层的ODataJsonLightPropertySerializer会在模型尝试写入时检查模型是否存在。 It calls the ValidatePropertyDefined method in the Microsoft.Data.OData.WriterValidationUtils class. 它调用Microsoft.Data.OData.WriterValidationUtils类中的ValidatePropertyDefined方法。

internal static IEdmProperty ValidatePropertyDefined(string propertyName, IEdmStructuredType owningStructuredType)

This will fail you with the runtime exception: 这将使运行时异常失败:

The property 'UserName' does not exist on type 'YourNamespace.Models.Request'
. Make sure to only use property names that are defined by the type.","type":"Microsoft.Data.OData.ODataException"
,"stacktrace":" at Microsoft.Data.OData.WriterValidationUtils.ValidatePropertyDefined(String propertyName
, IEdmStructuredType owningStructuredType)\r\n at Microsoft.Data.OData.JsonLight.ODataJsonLightPropertySerializer
.WriteProperty(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty
, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties

Bottom line is that the property needs to be defined in the model in order to serialize it. 底线是需要在模型中定义属性才能对其进行序列化。 You could conceivably rewrite large portions of the serialization layer, but there are lots of internal/static/private/non-virtual bits in the OData framework that make that unpleasant. 您可以想象重写序列化层的大部分,但OData框架中有许多内部/静态/私有/非虚拟位使得令人不快。

A solution is ultimately presented in the way Breeze is forcing you to generate the model, though. 但是,最终会以Breeze强制您生成模型的方式呈现解决方案。 Assuming a code-first implementation, you can inject additional model metadata directly into the XmlDocument produced by EntityFramework. 假设代码优先实现,您可以将其他模型元数据直接注入到EntityFramework生成的XmlDocument中。 Take the method in the Breeze EdmBuilder, with some slight modifications: 采用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);
        }
    }
}

This will place the property into the metadata to be consumed by the OData layer and make any additional steps to promote serialization unnecessary. 这会将属性放入OData层要使用的元数据中,并且不需要任何其他步骤来促进序列化。 You will, however, need to be mindful of how you shape the model metadata, as any requirements/specs will be reflected in the client side validation in Breeze. 但是,您需要注意如何塑造模型元数据,因为任何需求/规范都将反映在Breeze中的客户端验证中。

I have validated the CRUD operations of this approach in the ODataBreezejs sample provided by Breeze. 我已经在Breeze提供的ODataBreezejs示例中验证了此方法的CRUD操作。 https://github.com/Breeze/breeze.js.samples/tree/master/net/ODataBreezejsSample https://github.com/Breeze/breeze.js.samples/tree/master/net/ODataBreezejsSample

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM