简体   繁体   中英

Entity Framework - Why Does This Work?

I'm sure most people ask why things don't work. I am goIng to mix it up by asking why this does work.

private SmokeFireDBEntities dbContext = null; 
private IList<MemberResponse> gridData = new List<MemberResponse>();

private void UserControl_Initialized(object sender, EventArgs e)
{
    this.dbContext = new SmokeFireDBEntities(); 
    var members = from m in dbContext.Members
                 where new[] { "A", "P", "S", "J" }.Contains(m.Class.ShortName)
                 orderby m.Line
                 select m;

    foreach (Member m in members)
    {
        MemberResponse mr = new MemberResponse();
        mr.MemberID = m.ID;
        mr.Member = m;
        this.gridData.Add(mr);
    }
    PercentageGrid.ItemsSource = this.gridData;
}

private void SaveButton_Click(object sender, RoutedEventArgs e)
{
    AlarmTotal at = new AlarmTotal();

    at.Month = Convert.ToByte(this.MonthField.Text);
    at.Year = Convert.ToInt16(this.YearField.Text);
    at.NumAlarms = Convert.ToInt16(this.TotalAlarmsField.Text);

    this.dbContext.AlarmTotals.AddObject(at);
    this.dbContext.SaveChanges();

    // WHY IS THE FOLLOWING CODE NOT NECESSARY???
    //foreach (MemberResponse mr in this.PercentageGrid.Items)
    //{
    //    mr.AlarmTotalID = at.ID;
    //    this.dbContext.MemberResponses.AddObject(mr);
    //}

    //this.dbContext.SaveChanges();  
}

<UserControl.Resources>
        <DataTemplate x:Key="NameColumnTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Member.LastName}" />
                <TextBlock Text=", " />
                <TextBlock Text="{Binding Path=Member.FirstName}" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="InputColumnTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Path=NumAttended}" Name="MonthResponse" Width="60" />
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>

    <Grid Background="WhiteSmoke" Height="353" Width="509">
        <TextBox Height="23" HorizontalAlignment="Left" Margin="12,33,0,0" Name="MonthField" VerticalAlignment="Top" Width="75" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="93,33,0,0" Name="YearField" VerticalAlignment="Top" Width="59" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="158,33,0,0" Name="TotalAlarmsField" VerticalAlignment="Top" Width="115" />

        <ListView Margin="1,67,0,0" Name="PercentageGrid" ItemsSource="Binding" HorizontalAlignment="Stretch" Width="507" Height="286" VerticalAlignment="Stretch">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Name" CellTemplate="{StaticResource NameColumnTemplate}" />
                        <GridViewColumn Header="Line#" DisplayMemberBinding="{Binding Path=Member.Line}" />
                        <GridViewColumn Header="Class" DisplayMemberBinding="{Binding Path=Member.Class.ShortName}" />
                        <GridViewColumn Header="Response" CellTemplate="{StaticResource InputColumnTemplate}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>

I have removed unnecessary code to shorten this a bit. I am completely new to C#, .NET and everything that goes with it. I am completely stumped as to why this works at all. When I call the first dbContext.SaveChanges() to save the record to "AlarmTotals" it also saves all the "MemberResponse" records at the same time, and even more amazingly has the correct AlarmTotals.ID field populated. This one is really throwing me off, I just can't understand how this works seemingly by magic.

Any insight and explaination would be greatly appreciated. I really want to understand what is going on here.

First off, your data context is not closed, and you are going to leak a TON of memory/bandwidth if you are not closing your database context connections. Please look into that.

Secondly, the method .SaveChanges() will figure out what the correct id is to assign depending on the database settings. If there is not a definition there such as auto-increment then it is possible that these ids will not be set correctly and you could be saving into the same ID and then throw an exception. The children will have their foreign keys assigned only by explicit association which you are already doing.

Edit:

In response to your comment, usually a using statement is used for managing contexts because it is clean code:

var members = new Members();
using( var context = new SmokeFireDBEntities())
{
 //use context how you would, i.e.
 members = from m in context.Members
             where new[] { "A", "P", "S", "J" }.Contains(m.Class.ShortName)
             orderby m.Line
             select m;
}//once this is hit the context is closed and you can feel safe about your connection

If this approach does not work with how long you want the connection open, you may also manually (although not highly suggested) close the connection on your own.

this.dbContext = new SmokeFireDBEntities(); 
var members = from m in dbContext.Members
             where new[] { "A", "P", "S", "J" }.Contains(m.Class.ShortName)
             orderby m.Line
             select m;
this.dbContext.Dispose();//this will close the connection for you, and if you need it re-opened then either call new Entities() again or use the using statement

To add to what others said, I guess that the "magic" happens in the line 5:

1 foreach (Member m in members)
2 {
3     MemberResponse mr = new MemberResponse();
4     mr.MemberID = m.ID;
5     mr.Member = m;
6     this.gridData.Add(mr);
7 }

This is the line that causes your new MemberResponse to attach to current EF ObjectContext (and subsequently, causes them to save on SaveChanges()). MemberResponse.Member is a EF navigation property.

But are you sure that saved MemberResponses have MemberResponse.AlarmTotalID set correctly? The code doesn't look like it is. The best way to understand what really happened is to put a breakpoint in the setter of AlarmTotalID property .

Short answer: That's what the Context and the Entiities do; they track all that stuff so that updates to the whole object graph are saved.

By object graph, I mean the "root" object you are working with, and any related items that you might attach, whether you always realize that's what you are doing or not.

It is awesome!

EDIT: I recommend reading the excellent work of Julia Lerman if you want to dig more into Entity Framework. It's a very big topic, but it's worth it. She has a canonical book named Entity Framework, and an msdn column, blog, etc.

Note that questions about books are not really on topic here, but I'm suggesting this because you seem new to EF.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM