Entity framework EF CTP5无法更新可选导航属性

Entity framework EF CTP5无法更新可选导航属性,entity-framework,entity-framework-ctp5,Entity Framework,Entity Framework Ctp5,我在“项目”和“模板”之间有多对一的关联 项目具有类型为“模板”的属性 关联不是双向的(“模板”不知道“项目”) “项目”上关联的实体映射为: 如果我在没有指定模板的情况下创建了一个“项目”,那么null将正确地插入到“项目”表的“TemplateId”列中 如果指定模板,则正确插入模板的Id。生成的SQL语句: update [Projects] set [Description] = '' /* @0 */, [UpdatedOn] = '2011-01-16T14:30

我在“项目”和“模板”之间有多对一的关联

项目具有类型为“模板”的属性

关联不是双向的(“模板”不知道“项目”)

“项目”上关联的实体映射为:

如果我在没有指定模板的情况下创建了一个“项目”,那么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)
update [Projects]
set    [UpdatedOn] = '2011-01-16T14:32:14.00' /* @0 */
where  ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @1 */)
但是,如果我尝试更改模板,甚至将其设置为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)
update [Projects]
set    [UpdatedOn] = '2011-01-16T14:32:14.00' /* @0 */
where  ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* @1 */)
如您所见,TemplateId没有更新

这对我来说毫无意义。我甚至在我的代码中尝试过显式地将“Project”的“Template”属性设置为null,当您在代码中单步执行时,您可以看到它完全没有效果

谢谢, 本

[更新]

最初,我认为这是由于我忘记在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);
    }
}
公共类PortfolioContext:DbContext,IDbContext
{
公共PortfolioContext(字符串connectionStringName):基(connectionStringName){}
公共IDbSet Foos{get;set;}
公共IDbSet条{get;set;}
模型创建时受保护的覆盖无效(System.Data.Entity.ModelConfiguration.ModelBuilder ModelBuilder){
添加(newfoomap());
modelBuilder.Configurations.Add(newbarmap());
基于模型创建(modelBuilder);
}
public new IDbSet Set(),其中tenty:class{
返回base.Set();
}
}
公开课Foo{
公共Guid Id{get;set;}
公共字符串名称{get;set;}
公共虚拟条{get;set;}
公共食物(
{
this.Id=Guid.NewGuid();
}
}
公共类酒吧
{
公共Guid Id{get;set;}
公共字符串名称{get;set;}
公共酒吧()
{
this.Id=Guid.NewGuid();
}
}
公共类FooMap:EntityTypeConfiguration
{
公共地图()
{
这是ToTable(“Foos”);
this.HasKey(f=>f.Id);
这是可选的(f=>f.Bar);
}
}
公共类条形图:EntityTypeConfiguration
{
公共条形图()
{
这是一个可折叠的(“棒”);
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();
}
[测试]
公共无效模板_测试()
{
var ctx=新的PortfolioContext(“投资组合”);
var foo=new foo{Name=“foo”};
var bar=新条{Name=“bar”};
foo.Bar=Bar;
ctx.Set().Add(foo);
ctx.SaveChanges();
对象fooId=foo.Id;
对象barId=bar.Id;
ctx.Dispose();
var ctx2=新的PortfolioContext(“投资组合”);
var dbFoo=ctx2.Set().Find(fooId);
dbFoo.Bar=null;//不更新
ctx2.SaveChanges();
}

请注意,这是使用SQL CE 4。

如果这是EF CTP5中的一个错误(而不是我的代码:p),那么我提出了两种解决方法

1) 使关联具有双向性。在我的例子中,这意味着将以下内容添加到我的ProjectTemplate类中:

public virtual ICollection<Project> Projects {get;set;}
2) 第二个选项(我更喜欢,但它仍然有味道)是在域对象上添加外键。在我的情况下,我必须在项目中添加以下内容:

    public Guid? TemplateId { get; set; }
    public virtual ProjectTemplate Template { get; set; }
确保外键是可为空的类型

然后,我不得不更改映射,如下所示:

this.HasOptional(p => p.Template)
.WithMany()
.HasForeignKey(p => p.TemplateId);
然后,为了将模板设置为null,我向Project添加了一个helper方法(实际上,只需将外键设置为null即可):

我不能说我对外键污染我的域模型感到高兴,但我找不到任何替代方案

希望这有帮助。
Ben

好的,在对导航属性执行任何操作之前,只需加载导航属性。通过加载它,实际上是将其注册到ObjectStateManager,EF将通过该ObjectStateManager生成作为SaveChanges()结果的update语句

此代码将导致:

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'

请发布您的对象模型以及无法按预期工作的代码,以便我们重现您的问题。我们无法使用“简化模型版本”重新编程。@Morteza,不要在这方面浪费任何时间。我忘记将ProjectTemplate的IDbSet属性添加到我的DbContext中。直到我开始创建一个完整的示例来演示“bug”(咳嗽)时,我才发现了我的错误。这正是我的意思。另外,您已经在模型中使用了继承,它的行为可能会完全不同,这取决于所选择的策略。无论如何,谢谢你的更新,我很高兴你解决了这个问题:)@Morteza,不幸的是,这是短暂的。虽然我最初的测试是有效的,但它并没有准确地测试我的web应用程序中发生了什么(基本上我使用的是每个请求的上下文)。我已经用一个完整的测试用例更新了我的帖子,它复制了这个问题。非常感谢您的时间。@Morteza,是否可以用Include表达式来实现这一点。我们使用一个通用存储库,尽管我可以为项目创建一个特定的实现,但我仍然无法直接访问ObjectContext(我们围绕我们的上下文构建了一个包装器对象,IDbContext,但我们可以访问IDbSets)。当然可以。我使用Load方法的唯一原因是因为您使用Find()方法来加载dbFoo,并且我希望尽可能少地更改您的代码。@Morteza-我可以确认这是有效的。这是可选单向关联的预期行为吗?它与您的关联是单向的或可选的无关。正如我所说,update语句是基于ObjectStateManager(OSM)跟踪的CurrentValue生成的。如果您不加载导航属性,OSM将不跟踪它,因此它将被忽略。对于您不知道该属性的情况,您是否可以将其设置为常规?
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'