Использование сущностей LINQ в службах WCF


Технология LINQ для SQL сама по себе представляет большой интерес, поскольку позволяет проверять схему базы данных и запросы к базе данных на этапе компиляции, избавиться от поддержки хранимых процедур в базе данных и соответствия кода слоя логики хранимым процедурам в базе данных, в результате – сохранить время уменьшив объем ручной работы. В свою очередь технология WCF позволяет создавать службы, описывая лишь данные и контракты самих служб, не вдаваясь в детали реализации того или иного протокола передачи данных и сериализации объектов, позволяя декларативно в конфигурационном файле менять адреса и протоколы, используемые службой. Таким образом, WCF позволяет сократить время разработки и уменьшить объем кода, который должен написать разработчик.

При попытке использовать сущности LINQ в качестве контрактов передаваемых данных, приходится писать дополнительный код. В этой статье представлен один из способов связать сущности LINQ и службы WCF.

Для начала представим себе стандартную архитектуру WCF службы, состоящую из слоя данных (сборка, содержащая сущности LINQ), собственно службы (сборка, содержащая код службы) и хоста службы (пусть это будет сборка, содержащая консольное приложение). Для того, чтобы сгенерировать набор сущностей LINQ, достаточно добавить .dbml файл в проект слоя данных, открыть его в дизайнере и перетащить на него таблицы из Server Explorer. В этой статье в качестве примера будет использована база данных Northwind.

После этого сущности LINQ уже можно использовать, например, так:

NorthwindDataContext dc = new
ColiseumDataContext();
var product = dc.Products.Single(p => p.ProductID == productId);

 

Однако, для того, чтобы использовать эти сущности в WCF, необходимо пометить соответствующие классы и свойства атрибутами DataContract и DataMember, соответственно. Для этого существует специальный параметр в свойствах дизайнера сущностей LINQ: Serialization. Чтобы установить этот атрибут, можно щелкнуть по дизайнеру, открыть свойства (обычно F4, либо щелчок правой кнопкой мыши и пункт Properties) и установить свойство Serialization = Unidirectional. В результате, будут генерироваться классы, уже размеченные необходимыми атрибутами, например:

    [Table(Name="dbo.[Order Details]")]

    [DataContract()]

    public
partial
class
Order_Detail : INotifyPropertyChanging, INotifyPropertyChanged
    {
    
        [Column(Storage="_OrderID", DbType="Int NOT NULL", IsPrimaryKey=true)]
        [DataMember(Order=1)]
        public
int OrderID
        {
            get
            {
                return
this._OrderID;
            }
            set
            {
                if ((this._OrderID != value))
                {
                    if (this._Order.HasLoadedOrAssignedValue)
                    {
                        throw
new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
                    }
                    this.OnOrderIDChanging(value);
                    this.SendPropertyChanging();
                    this._OrderID = value;
                    this.SendPropertyChanged("OrderID");
                    this.OnOrderIDChanged();
                }
            }
        }

и т.д. ……………………………

 

Теперь эти объекты можно использовать в WCF службах. Например, создадим простую службу, которая работает с объектами Orders. Для этого определим интерфейс (контракт) службы и, собственно службу:

[ServiceContract]

public
interface
IMobileService
{
[OperationContract]

Order GetOrder(int orderId);
[OperationContract]

void UpdateOrder(Order obj);
}


public
class
MobileService : IMobileService
{

public
Order GetOrder(int orderId)
{

return
OrdersMapper.GetOrder(orderId);
}

public
void UpdateOrder(Order obj)
{

OrdersMapper.UpdateOrder(obj);
}

Однако, если с получением данных на стороне клиента проблем нет, то с обновлением на стороне сервиса возникает сложность, поскольку десериализованный на сервере объект не связан с контекстом данных LINQ. В результате, все-таки придется написать немного кода для метода UpdateOrder:

public
static
class
OrdersMapper

{

public
static
Order GetOrder(int orderId)
{

return
DataUtility.Context.Orders.Single(o => o.OrderID == orderId);
}


public
static
void UpdateOrder(Order obj)
{

var dc = DataUtility.Context;

Order oldOrder = dc.Orders.Single(o => o.OrderID == obj.OrderID);
oldOrder.CustomerID = obj.CustomerID;
oldOrder.EmployeeID = obj.EmployeeID;
oldOrder.Freight = obj.Freight;


foreach (Order_Detail od in oldOrder.Order_Details)
{
dc.Order_Details.Remove(od);
}

oldOrder.Order_Details.Clear();


foreach (Order_Detail od in obj.Order_Details)
{

Order_Detail nod = new
Order_Detail();
nod.Discount = od.Discount;
nod.OrderID = od.OrderID;
nod.ProductID = od.ProductID;
nod.Quantity = od.Quantity;
nod.UnitPrice = od.UnitPrice;
oldOrder.Order_Details.Add(nod);
}

oldOrder.OrderDate = obj.OrderDate;
oldOrder.RequiredDate = obj.RequiredDate;
oldOrder.ShipAddress = obj.ShipAddress;
oldOrder.ShipCity = obj.ShipCity;
oldOrder.ShipCountry = obj.ShipCountry;
oldOrder.ShipName = obj.ShipName;
oldOrder.ShippedDate = obj.ShippedDate;
oldOrder.ShipPostalCode = obj.ShipPostalCode;
oldOrder.ShipRegion = obj.ShipRegion;
oldOrder.ShipVia = obj.ShipVia;
dc.SubmitChanges();
}
}

 

Как видно из приведенного примера, необходимо заново связать данные с сущностью LINQ, более того, при наличии связей (как в связке Order – Order_Details), необходимо также обработать и их. Например, в примере выше список Order_Details полностью пересоздается, чтобы не отслеживать изменения в каждом объекте.

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

 

 

Comments (2)
  1. Добрый вечер, Гайдар

    Все здорово,

    а как быть с оптимистической конкуренцией.

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

    Код  (101 пример LINQ) позволяет отслеживать такие ситуации, и при необходимости можно формировать подробное инфо о том, каккое проперти вызвало конфликт.

    Следовательно, необходимо передовать, и оригинальное значение проперти.

    Есть другие варианты?

    То есть получить сущность,

    отправить клиенту,

    клиент модифицирует, отправляет на сервер,

    обновляем,

    в случае конфликта, отправляем ФОЛТ КОНТРАКТ клиенту

    Фрагмент UpdateProperty()

     try

               {

                   db.SubmitChanges(ConflictMode.ContinueOnConflict);

               }

               catch (ChangeConflictException)

    {…}

  2. Здесь все зависит от логики приложения – если ваши объекты путешествуют по сети между службами и возможны варианты одновременной записи, то, по умолчанию, все пройдет на ура – т.е. кто последний, тот и прав.

    Как вариант, в коде, изменяющем значения объектов, проводить валидацию по последним изменениям, для этого достаточно нагрузить объект полем TimeStamp, куда писать время последнего изменения, которое сохранять в базе в момент сохранения объекта.

    Дальше по этому полю можно проверять объекты перед записью в БД.

    Например, ушли два объекта c временем последнего изменения t, т.е. объекты содержат данные: (1)-t, (2)-t, после того, как первый объект был сохранен в БД, то время в бд стало, например p != t, тогда при попытке сохранения второго объекта наш код валидации (if(p != t) throw (new Exception("Ага!")); или какой-то другой, отобъект следующее изменение. Можно пользователя (службу) нотифицировать о проблеме, чтобы она приняла решения (перезаписывать или нет).

    Проблема-то вся в том, что после "отсоединения" объекта (т.е. передачи между службами), он уже на сущность LINQ, а просто объект с некоторыми полями, соответственно автоматический трекинг не работает.

Comments are closed.

Skip to main content