EF CTP5 не удалось обновить необязательное свойство навигации
У меня есть связь один к одному между "Проектом" и "Шаблоном".
Проект имеет свойство типа "Шаблон".
Ассоциация не является двунаправленной ("Шаблон" не знает "Проект").
Мое сопоставление сущностей для ассоциации по "Проекту":
this.HasOptional(p => p.Template);
Если я создаю "Проект" без указания шаблона, то значение NULL правильно вставляется в столбец "TemplateId" таблицы "Проекты".
Если я укажу шаблон, то идентификатор шаблона будет вставлен правильно. Сгенерированный SQL:
update [Projects]
set [Description] = '' /* @0 */,
[UpdatedOn] = '2011-01-16T14:30:58.00' /* @1 */,
[ProjectTemplateId] = '5d2df249-7ac7-46f4-8e11-ad085c127e10' /* @2 */
where (([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @3 */)
and [ProjectTemplateId] is null)
Однако, если я пытаюсь изменить шаблон или даже установить его на ноль, то templateId не обновляется. Сгенерированный SQL:
update [Projects]
set [UpdatedOn] = '2011-01-16T14:32:14.00' /* @0 */
where ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @1 */)
Как видите, TemplateId не обновляется.
Это просто не имеет смысла для меня. Я даже пытался явно установить для свойства "Template" свойства "Project" значение "ноль" в моем коде, и при просмотре кода вы можете видеть, что оно абсолютно не действует!
Спасибо Бен
[Обновить]
Первоначально я думал, что это было вызвано тем, что я забыл добавить свойство IDbSet в свой DbContext. Однако теперь, когда я проверил это дальше, я не уверен в этом. Ниже приведен полный тестовый пример:
public class PortfolioContext : DbContext, IDbContext
{
public PortfolioContext(string connectionStringName) : base(connectionStringName) { }
public IDbSet<Foo> Foos { get; set; }
public IDbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new FooMap());
modelBuilder.Configurations.Add(new BarMap());
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class {
return base.Set<TEntity>();
}
}
public class Foo {
public Guid Id { get; set; }
public string Name { get; set; }
public virtual Bar Bar { get; set; }
public Foo()
{
this.Id = Guid.NewGuid();
}
}
public class Bar
{
public Guid Id { get; set; }
public string Name { get; set; }
public Bar()
{
this.Id = Guid.NewGuid();
}
}
public class FooMap : EntityTypeConfiguration<Foo>
{
public FooMap()
{
this.ToTable("Foos");
this.HasKey(f => f.Id);
this.HasOptional(f => f.Bar);
}
}
public class BarMap : EntityTypeConfiguration<Bar>
{
public BarMap()
{
this.ToTable("Bars");
this.HasKey(b => b.Id);
}
}
И тест:
[Test]
public void Template_Test()
{
var ctx = new PortfolioContext("Portfolio");
var foo = new Foo { Name = "Foo" };
var bar = new Bar { Name = "Bar" };
foo.Bar = bar;
ctx.Set<Foo>().Add(foo);
ctx.SaveChanges();
object fooId = foo.Id;
object barId = bar.Id;
ctx.Dispose();
var ctx2 = new PortfolioContext("Portfolio");
var dbFoo = ctx2.Set<Foo>().Find(fooId);
dbFoo.Bar = null; // does not update
ctx2.SaveChanges();
}
Обратите внимание, что это использует SQL CE 4.
2 ответа
Хорошо, вам просто нужно загрузить свойство навигации, прежде чем что-то с ним делать. Загружая его, вы, по сути, регистрируете его в ObjectStateManager, в который EF смотрит, чтобы сгенерировать оператор обновления в результате SaveChanges().
using (var context = new Context())
{
var dbFoo = context.Foos.Find(fooId);
((IObjectContextAdapter)context).ObjectContext.LoadProperty(dbFoo, f => f.Bar);
dbFoo.Bar = null;
context.SaveChanges();
}
Этот код приведет к:
exec sp_executesql N'update [dbo].[Foos]
set [BarId] = null
where (([Id] = @0) and ([BarId] = @1))
',N'@0 uniqueidentifier,@1 uniqueidentifier',@0='A0B9E718-DA54-4DB0-80DA-C7C004189EF8',@1='28525F74-5108-447F-8881-EB67CCA1E97F'
Если это ошибка в EF CTP5 (а не мой код:p), есть два обходных пути, которые я придумала.
1) Сделать ассоциацию двунаправленной. В моем случае это означало добавление следующего к моему классу ProjectTemplate:
public virtual ICollection<Project> Projects {get;set;}
После этого, чтобы установить для свойства "Template" проекта значение null, вы можете просто удалить проект из шаблона - немного назад, но он работает:
var project = repo.GetById(id);
var template = project.Template;
template.Projects.Remove(project);
// save changes
2) Второй вариант (который я предпочел, но он все еще пахнет) - добавить внешний ключ к вашему объекту домена. В моем случае я должен был добавить следующее к проекту:
public Guid? TemplateId { get; set; }
public virtual ProjectTemplate Template { get; set; }
Убедитесь, что внешний ключ имеет тип NULL.
Затем мне пришлось изменить свое отображение так:
this.HasOptional(p => p.Template)
.WithMany()
.HasForeignKey(p => p.TemplateId);
Затем, чтобы установить для шаблона значение null, я добавил вспомогательный метод в Project (на самом деле он работает, просто установив для внешнего ключа значение null):
public virtual void RemoveTemplate() {
this.TemplateId = null;
this.Template = null;
}
Я не могу сказать, что рад, что загрязнил мою модель домена внешними ключами, но я не смог найти альтернативы.
Надеюсь это поможет. Бен