Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
400 views
in Technique[技术] by (71.8m points)

asp.net mvc - Cleanly updating a hierarchy in Entity Framework

I'm submitting a form for a StoredProcedureReport, which has many StoredProcedureParameters. Create works fine, but trying to update has left me asking whether or not Microsoft can seriously be serious.

I come from a Rails background where @report.update_attributes(params[:report]) will know exactly what to do with whatever association data it finds within. From what I can tell, the .NET equivalent of this is TryUpdateModel, which looked promising. At first. So I tried it with some params like this

IDGUID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureName:GetUsers
Name:search again UPDATED
StoredProcedureReportParameters[0].IDGUID:d70008a5-aba3-7560-a6ef-30a5524fac72
StoredProcedureReportParameters[0].StoredProcedureReportID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureReportParameters[0].Name:RowsPerPage
StoredProcedureReportParameters[0].Label:rows
StoredProcedureReportParameters[0].StoredProcedureReportParameterDataTypeID:a50008a5-2755-54c0-b052-865abf459f7f
StoredProcedureReportParameters[0].StoredProcedureReportParameterInputTypeID:a50008a5-2955-a593-d00f-00cd4543babf
StoredProcedureReportParameters[0].DefaultValue:10
StoredProcedureReportParameters[0].AllowMultiple:false
StoredProcedureReportParameters[0].Hidden:false
StoredProcedureReportParameters[1].IDGUID:d70008a5-a7a3-e35e-28b6-36dd9e448ee5
StoredProcedureReportParameters[1].StoredProcedureReportID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureReportParameters[1].Name:PageNumber
StoredProcedureReportParameters[1].Label:page was MODIFIEIIEIEIED!!!
StoredProcedureReportParameters[1].StoredProcedureReportParameterDataTypeID:a50008a5-2755-54c0-b052-865abf459f7f
StoredProcedureReportParameters[1].StoredProcedureReportParameterInputTypeID:a50008a5-2955-a593-d00f-00cd4543babf
StoredProcedureReportParameters[1].DefaultValue:1
StoredProcedureReportParameters[1].AllowMultiple:false
StoredProcedureReportParameters[1].Hidden:false

I assumed that with all the primary and foreign keys set, EF would know how to update the StoredProcedureReportParameter objects when I do this:

var report = context.StoredProcedureReports.FirstOrDefault(r => r.IDGUID == reportID);

if (report != null)
{
    succeeded = TryUpdateModel(report);
    context.SaveChanges();
}

Now, if I place a breakpoint on context.SaveChanges(), my report object and its associated StoredProcedureReportParameters look just like I'd expect them to. The foreign and primary keys are set, ALL the values check out. But SaveChanges raises this error:

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

One of the suggestions in this message is that I should assign the foreign-key property a non-null value, but as I stated, StoredProcedureReportID has the correct value on both StoredProcedureReportParameter objects.

Other posts I've read that deal with Update operations loop over associations and attach them to the context. Is this really what I'm stuck doing? Is EF really that dense? I'm hoping for a .NET pro to show me the light here. There has to be an easier way than that.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It is not that hard. There are 2 main ways to work using EF: Attached entities and Detached entities.

Let's suppose that we have 2 entites:

public class Foo
{
     public int FooId { get; set; }

     public string Description { get; set; }

     public ICollection<Bar> Bars { get; set; }
 }

public class Bar
{
    public int BarId { get; set; }

    public string Description { get; set; }
}

INSERTING

var foo = new Foo()
{
    FooId = 1,
    Description = "as",
    Bars = new List<Bar>()
    {
        new Bar
        {
            BarId = 1,
            Description = "as"
        },
        new Bar
        {
            BarId = 2,
            Description = "as"
        },
        new Bar
        {
            BarId = 2,
            Description = "as"
        }
    }
};

ctx.Foos.Add(foo);
ctx.SaveChanges();

In the above example, EF will recognize the new items, and will insert all of them.

UPDATING (ATTACHED)

var foo = ctx.Foos.Include("Bars").Where(i => i.FooId == 1).FirstOrDefault();
foreach (var bar in foo.Bars)
{
    bar.Description = "changed";
}

ctx.SaveChanges();

Here we loaded foo and its bars from the context. They are already attached to the context. So, all we need to do is change the values and call SaveChanges(). Everything will work fine.

UPDATING (DETACHED)

var foo = new Foo
{
    FooId = 1,
    Description = "changed3",
    Bars = new List<Bar>
    {
        new Bar
        {
            BarId = 1,
            Description = "changed3"
        },
        new Bar
        {
            BarId = 2,
            Description = "changed3"
        }
    }
};

ctx.Entry(foo).State = EntityState.Modified;

foreach (var bar in foo.Bars)
{
    ctx.Entry(bar).State = EntityState.Modified;
}

ctx.SaveChanges();

Here, we are working with items which already exists in database. However, they were not loaded from EF (they are not attached). EF knows nothing about them. We need to attached all of them manually and tell EF that they are modified.

REMOVING (ATTACHED)

var foo = ctx.Foos.Include("Bars").Where(i => i.FooId == 1).FirstOrDefault();
var bar = foo.Bars.First();
foo.Bars.Remove(bar);
ctx.SaveChanges();

Load bars from EF and just remove them from the collection.

REMOVING (DETACHED)

var bar = new Bar
{
    BarId = 1
};

ctx.Entry(bar).State = EntityState.Deleted;
ctx.SaveChanges();

Here, Bar was not loaded from the context. So, we have to tell EF that it is deleted.


In your case, you are sending the updated object to an MVC Controller; so, you have to tell EF that StoredProcedureReport and StoredProcedureParameters are modified.

If all the properties are being modified you can use:

ctx.Entry(foo).State = EntityState.Modified;
//remember to do the same in all children objects

It will mark all properties as modified. Be aware that if some property was not set on view, it will be updated as an empty value.

If not all the properties are being modified, you have to specify which properties are. Like this:

context.Entry(foo).Property("Description").IsModified = true; 
context.Entry(foo).Property("AnotherProperty").IsModified = true; 

Hope it helps!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...