Как обновить новую запись до ее фиксации в EF4?

Я использую EF4 POCO и шаблоны UnitOfWork/repository с MVC 3. Я пытаюсь понять, как изменить новую запись, которая должна быть вставлена.

Мой сервисный метод вставки/обновления выглядит примерно так (репозиторий внедряется в конструктор сервиса через IoC):

public void UpdateData(Guid id, int newValue)
{
    MyPoco poco = _repository.FirstOrDefault(p => p.Id = id);

    if (poco == null)
    {
        poco = new Poco 
        {
            //set properties
        };

        _repository.Add(poco);
    }

    poco.SomeFieldToUpdate = newValue;
}

И мои изменения сохраняются через мой UnitOfWork в фильтре действия UseUnitOfWorkAttribute на моем контроллере:

void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
{
    var unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
    unitOfWork.Commit();
}

Конечно, это прекрасно работает, если это происходит только один раз для существующих или новых данных. И он отлично работает на нескольких проходах, если он уже существует.

Но если значение Guid не существует в таблице, то он пытается выполнить несколько вставок, если это вызывается несколько раз.

Так что это моя дилемма. Я понимаю, почему это не работает, я просто не уверен, как правильно это исправить. В принципе, мне нужно как-то получить ссылку на существующий POCO в UnitOfWork и как-то обновить его. Но UnitOfWork недоступен в моем сервисе (по задумке) — и я даже не уверен, что знаю, как вытащить объект из UoW и все равно обновить его.

Я делаю это неправильно или я упускаю из виду что-то простое здесь? Или у меня есть фундаментальный недостаток в том, как я это разработал? У меня такое чувство, что я делаю это сложнее, чем должно быть.

Заранее спасибо.


person Jerad Rose    schedule 19.03.2011    source источник
comment
Куда вы вставляете новый Poco в репозиторий и когда/где вы вызываете SaveChanges? Poco, который вы создаете в приведенном выше коде, попадает непосредственно в сборщик мусора, с объектом ничего не происходит. Я думаю, что для понимания проблемы не хватает важных фрагментов кода. Можете ли вы добавить немного больше?   -  person Slauma    schedule 19.03.2011
comment
Да, это была просто оплошность с моей стороны (было поздно). :) Это было добавлено.   -  person Jerad Rose    schedule 19.03.2011


Ответы (2)


Причина, по которой это происходит, заключается в том, что ваша сущность еще не сохранена, и вы выполняете запрос, чтобы получить ее. Запрос не найдет его в базе данных и корректно вернет null.

Вам не нужно использовать репозиторий / единицу работы / ObjectContex в качестве внутреннего хранилища несохраненных сущностей среди сервисных вызовов. Если вам это нужно, вы должны проверить дизайн вашего приложения и отрефакторить его, потому что, вероятно, что-то не так.

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

public MyPoco GetById(Guid id)
{
    MyPoco enity = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
        .Where(e => e.Entity != null && e.Entity.GetType() == typeof(MyPoco)))
        .Select(e => (MyPoco)e.Entity)
        .Where(p => p.Id == id)
        .SingleOrDefault();

    if (entity == null)
    {
        entity = context.MyPocos.FirstOrDefault(p => p.Id == id);
    }
}
person Ladislav Mrnka    schedule 19.03.2011
comment
Спасибо за вашу помощь. Итак, в моем дизайне у меня в основном эта проблема связана с работой с пользовательскими данными сеанса, хранящимися в моей БД, и я пытаюсь выполнять различные действия в разных местах (вход в систему, выход из системы, вход в фильтр действий), и эти действия разбросаны во всем моем приложении. Моя проблема в том, что все они слабо связаны, поэтому у меня нет простого способа сохранить соединение прицела. Какие-либо предложения? - person Jerad Rose; 19.03.2011
comment
Основываясь на вашем комментарии о том, что вам не нужно использовать репозиторий / единицу работы / ObjectContex в качестве внутреннего хранилища несохраненных сущностей среди вызовов службы. Я глубже изучил свой дизайн и решил сохранить свой Session poco в моем пользовательском UserPrincipal. Это не только дает мне междисциплинарный доступ, но и, кажется, в любом случае принадлежит этому месту. Спасибо, что бросили вызов моему дизайну. - person Jerad Rose; 21.03.2011

Вы устанавливаете идентификатор, который вы передаете в UpdateData, в качестве ключа нового объекта Poco, например:

poco = new Poco 
{
    Id = id;
    //set properties
};

Если да, вы можете запросить объект не с помощью FirstOrDefault, а с помощью TryGetObjectByKey в методе репозитория:

public Poco GetPocoByKey(Guid id)
{
    EntityKey key = new EntityKey("MyEntities.Pocos", "Id", id);
    object pocoObject;
    if (context.TryGetObjectByKey(key, out pocoObject))
        return (Poco)pocoObject;

    return null;
}

Преимущество в том, что TryGetObjectByKey сначала просматривает ObjectContext, если может найти объект с указанным ключом. Только если нет, то будет запрошена база данных. Поскольку вы добавляете новый Poco в контекст в первый раз, когда он не найден, TryGetObjectByKey должен найти его в контексте при повторном поиске объекта с тем же ключом, даже если он еще не был сохранен в базе данных. .

Изменить: это решение не работает!

Поскольку TryGetObjectByKey не находит ключ для объектов, которые находятся в состоянии added в ObjectContext, даже если ключ не является ключом, сгенерированным БД и предоставленным приложением (см. комментарии ниже).

person Slauma    schedule 19.03.2011
comment
Я боюсь, что это не будет работать с несохраненным объектом, потому что у него есть временный ключ. - person Ladislav Mrnka; 19.03.2011
comment
Ах я вижу. Это также относится к удостоверениям, которые НЕ генерируются автоматически в БД? Это то, что я имел в виду, потому что у poco, похоже, есть ключ Guid as (который обычно не создается БД, а генерируется в приложении). Разве EF не использует этот ключ в качестве окончательного невременного ключа, поскольку он знает, что в базе данных не будет назначен другой ключ? - person Slauma; 19.03.2011
comment
Я только что попытался вызвать GetObjectByKey для нового объекта, добавленного в контекст (но не сохраненного), и я получил исключение, поэтому, вероятно, нет, но, возможно, я сделал что-то не так. - person Ladislav Mrnka; 19.03.2011
comment
Да, я тоже проверял, не работает. Я добавил Edit в ответ в качестве предупреждения. - person Slauma; 19.03.2011