Web API OData V4开放类型 - 如何配置控制器和数据上下文

35

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

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

以下链接描述了使用Web API中odata v4的“Open Types”:

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

示例代码可以正常工作并提供我在实体上需要的动态属性行为。但是,该代码仅限于在后端使用硬编码的值列表。并没有清楚地说明如何从Entity Framework数据上下文中填充实体。

起初,似乎可以像在数据库中为每个租户创建一个特定于租户的视图那样简单,但问题在于扩展属性确实需要从列“展开”为键值对。因此,我想知道是否需要为“扩展”属性单独创建实体。因此,我可以针对我的POCOs拥有类似于以下内容的东西:

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. 应该怎样编写ItemController代码才能包括所有HTTP谓词(GET、POST、PUT、PATCH、DELETE)的ItemExtension?
  3. 我的数据上下文应该有哪些ItemExtension才能让其在后端访问扩展列?
  4. 如何在后端持久化扩展列以支持这一点。

至于我尝试过的内容——很多不起作用的东西,但我已经选择了以下方案(假设没有更好的方法):

  1. 为每个“可扩展”实体创建一个基本POCO,并为每个实体创建一个单独的“扩展”实体(就像上面的模型一样)。

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

我缺少的是连接数据和模型之间的一切,非常感谢您的帮助。


很好的问题,我现在真的陷入了困境,有人成功地在EF6后端中使用过OData v4开放类型吗? 注意:不是NHibernate...寻找一个EF6解决方案,我可以采用代码或模型优先。 - Chris Schaller
我目前正在使用EF上下文的连接来执行存储过程。然后,我必须迭代数据以创建开放类型。它可以工作,只是我希望在将数据返回给调用者之前不必在控制器中“处理”数据。 - snow_FFFFFF
事实证明,我卡在一个与此略有不同的问题上,我已经解决了所有工作,只是当运行时尝试应用传递的查询选项时,任何没有指定 $select 语句的 GET 调用都会失败...我的问题是,当没有指定 $select 时,这与 SQL select * 相同,只有运行时将所有列注入到选择参数中,包括 DynamicProperties。必须重写 EnableQuery...我不确定是否可以避免 '处理' 数据,否则我们可能会将自定义属性硬编码到 DTO 中。 - Chris Schaller
2个回答

1
如果您真的不想使用ORM创建存储过程实体类型对象类,您可以直接使用FOR JSON PATH从SQL存储过程本身检索JSON对象,并将其传递给客户端。
例如- 参考链接

感谢您的输入。关于从SQL生成JSON的信息很有用。然而,这里的目标是支持具有动态、租户特定自定义字段的完整ODATA功能。我的主要挫折是Microsoft ODATA实现支持V4“开放类型”,但EF实际上并不支持。 - snow_FFFFFF

0

现在我不再使用Entity Framework,因为它在缓存数据方面出现了错误。看看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);
        ...
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接