EF CTP5 无法更新可选导航属性
我在“项目”和“模板”之间有多对一的关联。
项目具有“模板”类型的属性。
该关联不是双向的(“模板”不了解“项目”)。
我对“项目”关联的实体映射是:
this.HasOptional(p => p.Template);
如果我创建“项目”而不指定模板,则 null 会正确插入到“项目”表的“TemplateId”列中。
如果我指定一个模板,则该模板的 ID 会被正确插入。生成的 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)
但是,如果我尝试更改模板甚至将其设置为 null,则 templateId 不会更新。生成的 SQL:
update [Projects]
set [UpdatedOn] = '2011-01-16T14:32:14.00' /* @0 */
where ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @1 */)
如您所见,TemplateId 未更新。
这对我来说没有意义。我什至尝试在代码中显式地将“Project”的“Template”属性设置为 null,当单步执行代码时,您可以看到它完全没有效果!
谢谢, Ben
[更新]
最初我认为这是由于我忘记在 DbContext 上添加 IDbSet 属性造成的。然而,现在我已经进一步测试了它,我不太确定。下面是一个完整的测试用例:
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。
I have a many to one association between "Project" and "Template".
Project has a property of type "Template".
The association is not bidirectional ("Template" has no knowledge of "Project").
My entity mapping for the association on "Project" is:
this.HasOptional(p => p.Template);
If I create a "Project" without specifying a template then null is correctly inserted into the "TemplateId" column of the "Projects" table.
If I specify a template then the template's Id is correctly inserted. The SQL generated:
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)
However, if I try to change the template or even set it to null, the templateId is not updated. The SQL generated:
update [Projects]
set [UpdatedOn] = '2011-01-16T14:32:14.00' /* @0 */
where ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @1 */)
As you can see, TemplateId is not updated.
This just does not make sense to me. I have even tried explicitly setting the "Template" property of "Project" to null in my code and when stepping through the code you can see it has absolutely no effect!
Thanks,
Ben
[Update]
Originally I thought this was caused by me forgetting to add the IDbSet property on my DbContext. However, now that I've tested it further I'm not so sure. Below is a complete test case:
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);
}
}
And the test:
[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();
}
Note that this is using SQL CE 4.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好的,您只需要在对其执行任何操作之前加载导航属性即可。通过加载它,您实际上将其注册到 ObjectStateManager,EF 会通过 ObjectStateManager 来生成更新语句作为 SaveChanges() 的结果。
该代码将导致:
Ok, you just need to load the navigation property before doing anything to it. By loading it you essentially register it with ObjectStateManager which EF looks into to generate the update statement as a result of SaveChanges().
This code will result in:
如果这是 EF CTP5 中的错误(而不是我的代码:p),我想出了两种解决方法。
1)使关联双向。就我而言,这意味着将以下内容添加到我的 ProjectTemplate 类中:
完成此操作后,为了将项目的“Template”属性设置为 null,您可以从模板中删除该项目 - 有点落后,但它可以工作:
2)第二个选项(我更喜欢,但仍然有味道)是在域对象上添加外键。就我而言,我必须将以下内容添加到项目中:
确保外键是可为空的类型。
然后,我必须像这样更改我的映射:
然后,为了将模板设置为 null,我向 Project 添加了一个辅助方法(它实际上只需将外键设置为 null 即可工作):
我不能说我我很高兴用外键污染我的域模型,但我找不到任何替代方案。
希望这有帮助。
本
If this is a bug in EF CTP5 (and not my code :p) there are two workarounds that I came up with.
1) Make the association Bi-Directional. In my case this meant adding the following to my ProjectTemplate class:
With this done, in order to set the "Template" property of project to null, you can just remove the project from the template - a little backward but it works:
2) The second option (which I preferred but it still smells) is to add the foreign key on your domain object. In my case I had to add the following to Project:
Make sure the Foreign key is a nullable type.
I then had to change my mapping like so:
Then, in order to set the Template to null, I added a helper method to Project (it does actually work just by setting the foreign key to null):
I can't say that I'm happy about polluting my domain model with foreign keys but I couldn't find any alternatives.
Hope this helps.
Ben