【问题标题】:Web API OData V4 Open Types - How to configure Controller and Data ContextWeb API OData V4 开放类型 - 如何配置控制器和数据上下文
【发布时间】:2015-12-15 18:27:16
【问题描述】:

我有一个包含 Web API OData 服务层的多租户应用程序。我有一个支持自定义字段的新要求,这对于每个租户都是唯一的,并且向我的表中添加通用的“customfield01”、“customfield02”列不够灵活。

我已经探索了多种方法来在后端描述和持久化自定义数据,但更具挑战性的部分似乎是扩展我的 odata 服务以包含每个租户不同的自定义字段。

以下链接描述了带有 Web API 的 odata v4 中的“开放类型”:

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/use-open-types-in-odata-v4

示例代码运行良好,并提供了我在实体上需要的动态属性行为。但是,该代码仅在后端使用硬编码的值列表。完全不清楚如何从实体框架数据上下文填充实体。

起初,对于每个租户来说,这似乎就像在数据库中拥有一个特定于租户的视图一样简单,但问题是扩展属性确实需要从列中“反透视”到 key-值对。因此,我想知道“扩展”属性是否需要一个单独的实体。所以,我的 POCO 可以有这样的东西:

public class Item
{
    [Key]
    public Guid ItemId { get; set; }

    public Guid TenantId { get; set; }

    // navigation property for the extension entity
    public virtual ItemExtension ItemExtension { get; set; }
}

public class ItemExtension
{
    [Key]
    public Guid ItemId { get; set; }    

    // dynamic properties for the open type
    public IDictionary<string, object> DynamicProperties { get; set; }}
}

但是,问题又变成了如何用我的数据上下文中的数据填充这些对象。再一次,我认为我可以对列进行反透视,但这不起作用,因为我可以为每个动态属性拥有不同的数据类型(这对我很重要)。

所以,我真的有几个问题:

  1. 上面的 POCO 模型对我想要完成的工作有意义吗?
  2. 包含所有 HTTP 动词(GET、POST、PUT、PATCH、DELETE)的 ItemExtension 的 ItemController 代码应该是什么样子
  3. 我的数据上下文应该为 ItemExtension 提供什么以允许它访问后端的扩展列
  4. 应如何将扩展列保留在后端以支持这一点。

就我所尝试的而言 - 很多东西都不起作用,但我已经解决了以下问题(假设没有更好的方法):

  1. 每个“可扩展”实体的基本 POCO,具有单独的“扩展” 每个实体(如上面的模型)

  2. 在后端,由于我需要无限的灵活性和强大的数据类型,我计划为每个租户/实体组合创建一个单独的扩展表(将命名为 [TenantId].[ItemExtension] 与每一列根据需要命名和键入)。

我缺少的是数据和模型之间的所有内容。任何帮助将不胜感激。

【问题讨论】:

  • 很好的问题,我现在真的很困惑,有没有人成功使用带有 EF6 后端的 OData v4 开放类型?注意:不是 NHibernate...寻找 EF6 解决方案我将首先采用代码或模型
  • 我目前正在做的是使用 EF 上下文的连接来执行存储过程。然后,我必须遍历数据以创建开放类型。它有效,我只是希望在将数据返回给调用者之前不必“处理”控制器中的数据。
  • 原来我被困在一个与此略有不同的问题上,除了运行时尝试应用查询选项时任何未指定 $select 语句的 GET 调用都失败之外,我一切正常传入的...我的问题是,当没有指定 $select 时,这与 SQL select * 相同,只有运行时将所有列注入到选择参数中,包括 DynamicProperties。必须覆盖 EnableQuery...我不确定您是否可以避免不得不“处理”数据,否则我们不妨将自定义属性硬编码到 DTO 中。

标签: c# asp.net entity-framework asp.net-web-api


【解决方案1】:

如果你真的不想使用 ORM 创建存储过程实体类型对象类,你可以简单地使用 FOR JSON PATH 从 SQL 存储过程本身检索 json 对象并将其扔给客户端。

例如 - Reference Link

【讨论】:

  • 感谢您的意见。关于从 SQL 生成 json 的好信息。但是,这里的目标是通过动态的、特定于租户的自定义字段来支持完整的 odata 功能。我的主要挫败感是 Microsoft odata 实现支持 V4“开放类型”,但 EF 确实不支持。
【解决方案2】:

现在我在缓存数据出错后不使用实体框架。看看 Fluent NHibernate。在 ORM 中,您可以调整 OData v4 动态属性到用户类型的映射。使用 nuget 包 Newtonsoft.Json。

你的班级:

public class Item
{
    [Key]
    public Guid ItemId { get; set; }

    // dynamic properties for the open type
    public IDictionary<string, object> DynamicProperties { get; set; }

    ... 
}

Fluent NHibernate 类的 StoreDynamicProperties 自定义类型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using Newtonsoft.Json;
using NHibernate.UserTypes;
using NHibernate.SqlTypes;

[Serializable]
public class StoreDynamicProperties : IUserType
{
    private JsonSerializerSettings _settings = new JsonSerializerSettings(); // { TypeNameHandling = TypeNameHandling.All };

    public new bool Equals(object x, object y)
    {
        if (x == null && y == null)
            return true;

        if (x == null || y == null)
            return false;

        var xdocX = JsonConvert.SerializeObject((IDictionary<string, object>)x, _settings);
        var xdocY = JsonConvert.SerializeObject((IDictionary<string, object>)y, _settings);

        return xdocY == xdocX;
    }

    public int GetHashCode(object x)
    {
        if (x == null)
            return 0;

        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        if (names.Length != 1)
            throw new InvalidOperationException("Only expecting one column…");

        var val = rs[names[0]] as string;

        if (val != null && !string.IsNullOrWhiteSpace(val))
        {
            return JsonConvert.DeserializeObject<IDictionary<string, object>>(val, _settings);
        }

        return null;
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        var parameter = (DbParameter)cmd.Parameters[index];

        if (value == null)
        {
            parameter.Value = DBNull.Value;
        }
        else
        {
            parameter.Value = JsonConvert.SerializeObject((IDictionary<string, object>)value, _settings);
        }
    }

    public object DeepCopy(object value)
    {
        if (value == null)
            return null;

        //Serialized and Deserialized using json.net so that I don't
        //have to mark the class as serializable. Most likely slower
        //but only done for convenience. 

        var serialized = JsonConvert.SerializeObject((IDictionary<string, object>)value, _settings);

        return JsonConvert.DeserializeObject<IDictionary<string, object>>(serialized, _settings);
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        var str = cached as string;

        if (string.IsNullOrWhiteSpace(str))
            return null;

        return JsonConvert.DeserializeObject<IDictionary<string, object>>(str, _settings);
    }

    public object Disassemble(object value)
    {
        if (value == null)
            return null;

        return JsonConvert.SerializeObject((IDictionary<string, object>)value);
    }

    public SqlType[] SqlTypes
    {
        get
        {
            return new SqlType[] { new StringSqlType(8000) };
        }
    }

    public Type ReturnedType
    {
        get { return typeof(IDictionary<string, object>); }
    }

    public bool IsMutable
    {
        get { return true; }
    }
}

在 ItemMap 类中:

using FluentNHibernate.Mapping;

public class ItemMap : ClassMap<Item>
{
    public ItemMap()
    {
        Table("Items");

        Id(item => item.ItemId)
            .GeneratedBy
            .GuidComb();

        Map(item => item.DynamicProperties)
            .CustomType<StoreDynamicProperties>()
            .Column("Properties")
            .CustomSqlType("varchar(8000)")
            .Length(8000);
        ...
    }
}

【讨论】:

    猜你喜欢
    • 2015-05-24
    • 1970-01-01
    • 2017-12-04
    • 2014-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    相关资源
    最近更新 更多