Службы RIA: вставка нескольких объектов модели презентации - PullRequest
2 голосов
/ 30 января 2010

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

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

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

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

1 Ответ

3 голосов
/ 14 мая 2010

Если вы создаете правильные родительско-дочерние ассоциации, вам просто нужно отследить отношения вставленной модели представления (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

...