Подтвердить что ты не робот

Типы API-интерфейсов OData V4 для Web API - Как настроить Контекст контроллера и данных

У меня есть приложение с несколькими арендаторами, которое включает в себя уровень сервиса OData Web API. У меня есть новое требование для поддержки настраиваемых полей, которые будут уникальны для каждого арендатора, и добавление общих таблиц "customfield01", "customfield02" в мои таблицы недостаточно гибко.

Я рассмотрел несколько способов описания и сохранения пользовательских данных в фоновом режиме, но более сложная часть, по-видимому, расширяет мои службы odata, добавляя настраиваемые поля, по-разному, для каждого арендатора.

Следующая ссылка описывает "Открытые типы" в odata v4 с Web API:

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

Но опять же возникает вопрос, как заполнить эти объекты данными из моего контекста данных. Еще раз, я подумал, что у меня может быть точка, чтобы раскрыть столбцы, но это не работает, потому что у меня могут быть разные типы данных (что важно для меня) для каждого динамического свойства.

Итак, у меня действительно есть несколько вопросов:

  • Означает ли модель POCO выше смысл того, что я пытаюсь выполнить?
  • Что должен иметь код ItemController для включения ItemExtension для всех HTTP-глаголов (GET, POST, PUT, PATCH, DELETE)
  • Что должен иметь контекст данных для элемента ItemExtension, чтобы он мог получить доступ к расширенным столбцам на внутреннем сервере.
  • Как следует сохранить расширенные столбцы на внутренней стороне, чтобы поддержать это.

Насколько я пробовал - много вещей, которые не работают, но я остановился на следующем (предполагая, что нет лучшего способа):

  • Базовый POCO для каждого "расширяемого" объекта с отдельным "расширением", сущность для каждого (например, модель выше)

  • На заднем конце, так как мне нужна неограниченная гибкость и сильные типы данных, я планирую иметь отдельную таблицу расширений для каждой комбинации Tenant/Entity (будет называться как [TenantId]. [ItemExtension] с каждым столбцом названный и введенный по мере необходимости).

То, что мне не хватает, - это все между моими данными и моей моделью. Любая помощь будет принята с благодарностью.

4b9b3361

Ответ 1

Теперь я не использую 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; }

    ... 
}

и StoreDynamicProperties для класса Fluent NHibernate:

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