Службы RIA: вставка нескольких объектов модели презентации

Я делюсь данными через службы RIA, используя модель представления поверх классов LINQ to SQL. В клиенте Silverlight я создал пару новых сущностей (альбом и исполнитель), связал их друг с другом (добавив альбом в коллекцию альбомов художника или установив свойство Artist в альбоме - либо один из них работает), добавил их в контекст, и представленные изменения.

На сервере я получаю два отдельных вызова Insert - один для альбома и один для исполнителя. Эти права являются новыми, поэтому для их значений идентификаторов установлено значение int по умолчанию (0 - имейте в виду, что в зависимости от моей БД это может быть действительный идентификатор в БД), поскольку, насколько я знаю, вы не устанавливаете идентификаторы для новых лиц на клиенте. Это все работало бы хорошо, если бы я передавал классы LINQ to SQL через мои службы RIA, потому что, хотя вставка Album включает в себя Artist, а вставка Artist включает Album, оба являются Entities, и контекст L2S распознает их. Однако, с моими объектами пользовательской модели представления, мне нужно преобразовать их обратно в классы LINQ to SQL, поддерживая ассоциации в процессе, чтобы их можно было добавить в контекст L2S.

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

Возможно ли это, или мне следует заняться другим дизайном?

1 ответ

Решение

Если вы создаете правильные связи родитель-потомок, вам просто нужно отследить отношения вставленной модели презентации (PM) и объекта:

РМ:

public class Parent
{
    [Key]
    public int? ParentID { get; set; }

    [Include]
    [Composition]
    [Association("Parent_1-*_Child", "ParentID", "ParentID", IsForeignKey = false)]
    public IEnumerable<Child> Children { get; set; }
}

public class Child
{
    [Key]
    public int? ChildID { get; set; }

    [Include]
    [Association("Parent_1-*_Child", "ParentID", "ParentID", IsForeignKey = true)]
    public Parent Parent { get; set; }
}

Обязательно используйте [Composition], чтобы заставить WCF RIA вызывать метод InsertChild на DomainService.

Silverlight:

...
public Child NewChild(Parent parent)
{
    return new Child
                {
                    ParentID = parent.ParentID,
                    Parent = parent,
                };
}
...
public void SubmitChanges()
{
    DomainContext.SubmitChanges(SaveComplete, null);
}
...

Если Parent не новый, у него будет ParentID. Если он новый, идентификатор родителя будет нулевым. Установив для Child.Parent ссылку на новый родительский объект, RIA понимает, что вы пытаетесь сделать, сохраняет ссылку после ее отправки на сервер.

DomainService на сервере:

[EnableClientAccess]
public class FamilyDomainService : DomainService
{
    private readonly IDictionary<object, EntityObject> _insertedObjectMap;

    public void InsertParent(Parent parent)
    {
        ParentEntity parentEntity = new ParentEntity();

        ObjectContext.AddToParents(parentEntity);
        _insertedObjectMap[parent] = parentEntity;

        ChangeSet.Associate(parent, parentEntity, (p, e) => p.ParentID = e.ParentID;
    }

    public void InsertChild(Child child)
    {
        var childEntity = new ChildEntity();

        if (child.ParentID.HasValue) // Used when the Parent already exists, but the Child is new
        {
            childEntity.ParentID = child.ParentID.GetValueOrDefault();
            ObjectContext.AddToChildren(childEntity);
        }
        else // Used when the Parent and Child are inserted on the same request
        {
            ParentEntity parentEntity;
            if (child.Parent != null && _insertedObjectMap.TryGetValue(child.Parent, out parentEntity))
            {
                parentEntity.Children.Add(childEntity);
                ChangeSet.Associate(child, childEntity, (c, e) => c.ParentID = e.Parent.ParentID);
            }
            else
            {
                throw new Exception("Unable to insert Child: ParentID is null and the parent Parent cannot be found");
            }
        }

        _insertedObjectMap[child] = childEntity;

        ChangeSet.Associate(child, childEntity, (c, e) => c.ChildID = e.ChildID );
    }

    protected override bool PersistChangeSet()
    {
        ObjectContext.SaveChanges();
        _insertedObjectMap.Clear();
        return true;
    }
}

Две важные части здесь. Во-первых, _insertedObjectMap хранит отношения между вновь вставленными объектами, для которых не установлен идентификатор. Поскольку вы делаете это в транзакции и в одном обращении к БД, идентификатор будет установлен только после того, как все объекты будут вставлены. Сохраняя взаимосвязь, дочерний PM может найти версию сущности родительского PM, используя базу данных. Дочерний объект добавляется в коллекцию Children в родительском объекте, и LINQToSQL или LINQToEnityFramework должны обрабатывать внешний ключ для вас.

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

Моя информация из ChangeSet.Associate() пришла от: http://blogs.msdn.com/deepm/archive/2009/11/20/wcf-ria-services-presentation-model-explained.aspx

Другие вопросы по тегам