Использование перечисляемых типов в ADO.NET Data Services

Этой осень довелось пообщаться на тему ADO.NET Data Services в рамках Ижевского сообщества разработчиков. Кроме прочих обсуждаемых вопросов, речь зашла об использовании перечисляемых типов в составе сущности, которая используется в рамках сервиса ADO.NET Data Services. Поскольку в течении этого времени вопросом интересовалось еще несколько человек, решил вынести эту информацию сюда.

Суть проблемы заключается в следующем. Предположим мы решили создать сервис на базе ADO.NET Data Services и использовать в его источнике сущность, содержащую поле перечисляемого типа. Обратившись к такому сервису можно увидеть незамысловатую ошибку.

Можно посмотреть детали этой ошибки. Для этого необходимо соответствующим образом настроить сервис.

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WebDataService1 : DataService<MyDataContext>
{

Обратившись к сервису снова, мы увидим ошибку, которая говорит о том, что перечисляемые типы нельзя использовать в сущностях для подобных сервисах. К сожалению, эта досадная недоработка в ADO.NET Data Services может серьезно подпортить жизнь разработчикам и, как я уже сказал, нет стандартного решения этой проблемы. Давайте попробуем решить эту проблему хотя бы частично.

Для этого создадим сущность и перечисляемый тип, который будем использовать в сервисе:

public enum StateEnum
{
    Normal = 1,
    Error = 2
}

[DataServiceKey("ID")]
public class SampleEntity
{
    public int ID { get; set; }

    public string Name { get; set; }

    public StateEnum State
    {
        get;
        set;
    }
}

Теперь создадим собственный контекст данных и стандартный сервис ADO.NET Data Services. Пусть этот контекст содержит статические данные. В вашем случае это может быть контекст с собственной логикой.

public class MyDataContext
{
    private static readonly IEnumerable<SampleEntity> Entities = new List<SampleEntity>()
     {
        new SampleEntity() {ID = 1, Name = "Name1", State = StateEnum.Normal},
        new SampleEntity() {ID = 2, Name = "Name2", State = StateEnum.Error},
        new SampleEntity() {ID = 3, Name = "Name3", State = StateEnum.Normal},
        new SampleEntity() {ID = 4, Name = "Name4", State = StateEnum.Normal},
        new SampleEntity() {ID = 5, Name = "Name5", State = StateEnum.Normal},
    };

    public IQueryable<SampleEntity> Data
    {
        get
        {
            return Entities.AsQueryable<SampleEntity>();
        }
    }
}

Понятно, что с таким сервисом мы получим ошибку, которую видел в самом начале. Чтобы избежать ошибки мы будем использовать атрибут IgnoreProperties. С его помощью можно указать ADO.NET Data Services о том, что нужно игнорировать указанные свойства. Наша сущность примет следующий вид.

[DataServiceKey("ID")]
[IgnoreProperties("State")]
public class SampleEntity
{
    public int ID { get; set; }

    public string Name { get; set; }

    public StateEnum State
    {
        get { return (StateEnum)StateValue; }
        set { StateValue = (int)value; }
    }
}

Отлично, ошибка перестала появляться, но при этом значение данного свойства также перестало передаваться на клиентскую сторону.

<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 
<feed xml:base="http://localhost:4321/WebDataService1.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
    <title type="text">Data</title> 
    <id>http://localhost:4321/WebDataService1.svc/Data</id> 
    <updated>2009-11-29T06:06:57Z</updated> 
    <link rel="self" title="Data" href="Data" /> 
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(1)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:06:57Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(1)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">1</d:ID> 
                <d:Name>Name1</d:Name> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(2)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:06:57Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(2)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">2</d:ID> 
                <d:Name>Name2</d:Name> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(3)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:06:57Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(3)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">3</d:ID> 
                <d:Name>Name3</d:Name> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(4)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:06:57Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(4)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">4</d:ID> 
                <d:Name>Name4</d:Name> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(5)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:06:57Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(5)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">5</d:ID> 
                <d:Name>Name5</d:Name> 
            </m:properties>
        </content>
    </entry>
    </feed>

Для того, чтобы все-таки передавать это значение на клиент достаточно вспомнить, что значение перечисляемого типа — это, по сути, целое число, а это простой тип, который можно передавать через сервис. Таким образом, делаем небольшое дополнение к этой сущности и получаем следующее её определение.

[DataServiceKey("ID")]
[IgnoreProperties("State")]
public class SampleEntity
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int StateValue { get; set; }

    public StateEnum State
    {
        get { return (StateEnum)StateValue; }
        set { StateValue = (int)value; }
    }
}

После этого, сервис, работающий с указанной сущностью будет игнорировать свойство State, но при этом на клиент будет передаваться StateValue. В этом легко убедиться, если обратиться к сервису через браузер.

<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 
<feed xml:base="http://localhost:4321/WebDataService1.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
    <title type="text">Data</title> 
    <id>http://localhost:4321/WebDataService1.svc/Data</id> 
    <updated>2009-11-29T06:13:47Z</updated> 
    <link rel="self" title="Data" href="Data" /> 
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(1)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:13:47Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(1)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">1</d:ID> 
                <d:Name>Name1</d:Name> 
                <d:StateValue m:type="Edm.Int32">1</d:StateValue> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(2)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:13:47Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(2)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">2</d:ID> 
                <d:Name>Name2</d:Name> 
                <d:StateValue m:type="Edm.Int32">2</d:StateValue> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(3)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:13:47Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(3)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">3</d:ID> 
                <d:Name>Name3</d:Name> 
                <d:StateValue m:type="Edm.Int32">1</d:StateValue> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(4)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:13:47Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(4)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">4</d:ID> 
                <d:Name>Name4</d:Name> 
                <d:StateValue m:type="Edm.Int32">1</d:StateValue> 
            </m:properties>
        </content>
    </entry>
    <entry>
        <id>http://localhost:4321/WebDataService1.svc/Data(5)</id> 
        <title type="text" /> 
        <updated>2009-11-29T06:13:47Z</updated> 
        <author>
        <name /> 
        </author>
        <link rel="edit" title="SampleEntity" href="Data(5)" /> 
        <category term="WebApplication69.SampleEntity" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> 
        <content type="application/xml">
            <m:properties>
                <d:ID m:type="Edm.Int32">5</d:ID> 
                <d:Name>Name5</d:Name> 
                <d:StateValue m:type="Edm.Int32">1</d:StateValue> 
            </m:properties>
        </content>
    </entry>
</feed>

Последнее, что осталось сделать — это сообщить клиентской библиотеке о том, как воспринимать подобные значения. Для этого на клиентской стороне также объявим нужный перечисляемый тип, в частичном классе добавим нужное свойство и укажем атрибут IgnoreProperties.

public enum StateEnum
{
    Normal = 1,
    Error = 2
}

[IgnoreProperties("State")]
public partial class SampleEntity
{
    public StateEnum State
    {
        get { return (StateEnum)StateValue; }
        set { StateValue = (int)value; }
    }
}

Теперь на клиентской стороне можно обращаться к нужному свойству и получать значение перечисляемого типа. При этом сервис будет работать корректно.

class Program
{
    static void Main(string[] args)
    {
        var context = new MyDataContext(new Uri(@"http://localhost:4321/WebDataService1.svc/"));
        foreach (var entity in context.Data)
        {
            Console.WriteLine(entity.State);
        }
        Console.Read();
    }
}

Конечно, представленное решение не претендует на элегантное решение. Однако, если сильно хочется использовать перечисляемые типы, то это может быть одним из решений, которое можно использовать в приложениях.