【问题标题】:Retrieve and update record with Entity Framework in web app在 Web 应用程序中使用实体框架检索和更新记录
【发布时间】:2013-09-26 22:56:24
【问题描述】:

我是开发新手,并尝试使用 Entity Framework 5.0(数据库优先方法)检索要在 ASP.NET Web 应用程序页面上的 FormView 中显示和编辑的记录,但我不确定最好的方法去吧。

要检索记录,我使用以下代码:

protected void Page_Load(object sender, EventArgs e)
{
    LoadData(int.Parse(Session["PersonID"].ToString()));
}

private void LoadData(int iPersonID)
{
    using (PeopleEntities ctx = new PeopleEntities())
    {
        var query = (from a in ctx.People
                     where a.PersonID == iPersonID
                     select a).FirstOrDefault();

        TextBoxFirstName.Text = query.FirstName;
        TextBoxLastName.Text = query.LastName;
    }
}

为了保存它,我使用:

protected void ButtonSave_Click(object sender, EventArgs e)
{
    SaveEmployee(int.Parse(Session["PersonID"].ToString()));
}

private void SaveEmployee(int iPersonID = 0)
{
    using (PeopleEntities ctx = new PeopleEntities())
    {  
        var query = (from a in ctx.People
                     where a.PersonID == iPersonID
                     select a).FirstOrDefault();

        query.FirstName = TextBoxFirstName.Text;
        query.LastName = TextBoxLastName.Text;
        ctx.SaveChanges();
    }
}

让这两种方法分别查询数据库以检索和更新记录对我来说似乎很愚蠢,但是,我还是个新手,也许我只是遗漏了一些东西。有没有办法用实体填充 FormView 上的控件,并有一种方法来保存记录,而无需根据状态手动分配值(query.FirstName = TextBoxFirstName.Text 等)?

我已经看过 EntityDataSource,但我认为除了最简单的事情之外,这不是一个好的选择。

谁能告诉我我正在做的事情是否可以或提供更好的示例或指导?

非常感谢您的帮助!

【问题讨论】:

    标签: c# asp.net entity-framework linq-to-entities


    【解决方案1】:

    恕我直言,最好的方法是,当您仅将数据检索到 DISPLAY 时,不要进行更改跟踪。这将避免性能问题。因此,请使用AsNoTracking 方法来避免更改跟踪代理。

    对于更新,您应该加载启用更改跟踪,这就是为什么在保存部分没有调用AsNoTracking

    记得检查空值。您使用的是 FirstOrDefault,但由于您使用的是主键,因此不会有第二条记录,因此只需使用 SingleOrDefault。但由于可能发生默认 (null),请检查 null 值。

    另外,使用 lambda 表达式。一开始它们并不那么容易上手,但你会花一点力气就习惯了,它们会大大简化你的代码。

    但是根据您的问题,有一些解决方法可以避免这种情况,但它们不是最好的方法。您应该避免长期存在的实体,而更喜欢将 ViewModel 用于长期存在的对象,并为存储库和持久实体考虑 UnitOfWork 模式。

    如果你真的想要,你可以从上下文中Detach你的实体,在任何地方使用它,当你准备好时,Attach它回来并将它的状态设置为Modified。 为此,请看这里:http://msdn.microsoft.com/en-us/library/bb896271.aspx

    根据你的情况,我建议这样做:

    private void LoadData(int iPersonID)
    {
       using (PeopleEntities ctx = new PeopleEntities())
       {
          // AsNoTracking will avoid performance hit of change-tracking here...
          // Since we're building-up a view, not an update case yet, you don't have to create
          // proxies that will check for entity changing...
          var query = ctx.People.AsNoTracking().SingleOrDefault(_people => _people.PersonID == iPersonID)
    
          // Rendering comes into action
          if (query != null)
          {
             TextBoxFirstName.Text = query.FirstName;
             TextBoxLastName.Text = query.LastName;
          }
       }
    }
    
    private void SaveEmployee(int iPersonID = 0)
    {
       using (PeopleEntities ctx = new PeopleEntities())
       {  
          var query = ctx.Prople.SingleOrDefault(_person => _person.PersonID == iPersonID);
    
          if (query != null)
          {
             query.FirstName = TextBoxFirstName.Text;
             query.LastName = TextBoxLastName.Text;
             ctx.SaveChanges();
          }
       }
    }
    

    【讨论】:

    • 使用 SelectSingleOrDefault(c => c.id == myid)
    【解决方案2】:

    这也是我的做法。有必要检索您要更新的对象。

    【讨论】:

      【解决方案3】:

      “让这两种方法分别查询数据库对我来说似乎很愚蠢 检索和更新记录”

      你是绝对正确的Don't Repeat Yourself应该是一个口头禅 你应该努力遵循的原则

      这里您选择在页面加载事件中检索数据并在按钮单击事件中再次检索。这两个事件都发生在同一个网页实例中。您可以将其存储在实例变量中并在单击按钮时重新使用它,或者您可以为“延迟加载”的实体设置一个属性。有各种各样的方法。延迟加载 probably 在这里肯定是矫枉过正,因为您可能只会使用 PageLoad 中的属性 应该了解何时需要访问数据库以及何时不需要。

      第一次加载页面时,需要去数据库获取你想要显示的数据。此后,当页面回发时,数据通常会以表单值的形式出现。

      当您更新记录时,还需要访问数据库 - 在此示例中,当您的用户单击保存按钮时会发生这种情况。

      这是一个我可能不应该提到的延迟加载的例子:

      private People _Person;
      
      //lazy loaded property
      private People Person
      {
          get
          {
              if (_Person == null)
                  using (PeopleEntities ctx = new PeopleEntities())
                      _Person = GetPerson(ctx);
      
              //returning a Person that isn't updateable because we've disposed of the context    
              return _Person;
          }
      }
      
      //Retrieve an updateable person
      private static object GetPerson(PeopleEntities ctx)
      {
          return (from a in ctx.People
                  where a.PersonID == int.Parse(Session["PersonID"]
                  select a).FirstOrDefault();
      }
      

      您的代码存在的另一个问题是您总是根据数据库中的值在 PageLoad 事件中设置 TextBoxes。这意味着当您到达ButtonSave_Click 事件时,回发的值已被数据库中的内容覆盖,并且不会保存更改!

      所以你应该这样做:

      protected void Page_Load(object sender, EventArgs e)
      {
          if(!IsPostBack)//Only do this first time it's loaded
          {
             TextBoxFirstName.Text = Person.FirstName;
             TextBoxLastName.Text = Person.LastName;
          }
      }
      

      你的按钮点击看起来像这样:

      protected void ButtonSave_Click(object sender, EventArgs e)
      {
          SavePerson(TextBoxFirstName.Text, TextBoxLastName.Text);
      }
      
      private SavePerson(string firstName, string lastName)
      {
         using (PeopleEntities ctx = new PeopleEntities())
         {
            var person = GetPerson(ctx);
            person.FirstName = firstName;
            person.LastName = lastName;
            ctx.SaveChanges();
         }
      }
      

      随着编码的进行,您会发现您想在其他页面上重复 SavePersonGetPerson 代码。 - 那是你开始引入存储库或层的时候。不要忘记 mantra 你应该努力遵循的原则并移动代码到另一个类,以便您可以重新使用它。

      该类应该在PeopleRepository 或其他层中。最终你会发现PeopleRepository 中的代码看起来很像MantraRepository 中的代码,并且你会想要停止为不同的类型重复自己。

      这时你应该开始使用“泛型”。您将PeopleRepositoryMantraRepository 替换为Repository<People>Repository<Mantra>,代码在一个类中,定义类似于public class BaseRepository<T>

      不过,在您踏上这段旅程之前,还有关于实体框架位的另一件事 - 而不是

      var query = (from a in ctx.People where a.PersonID == iPersonID select a).FirstOrDefault();

      你应该/可以使用

      var query = ctx.People.Find(iPersonID)

      来自此来源:Querying/Finding Entities

      "DbSet 上的 Find 方法使用主键值来尝试查找 由上下文跟踪的实体。如果实体不在 上下文然后查询将被发送到数据库以查找实体 那里。如果在上下文中找不到实体,则返回 Null 或 在数据库中。

      查找与使用查询在两个重要方面不同:

      只有当实体具有 在上下文中找不到给定的键。 Find 将返回实体 处于已添加状态。也就是说,Find 将返回具有 已添加到上下文中,但尚未保存到数据库中。”

      现在,如果您想进行更改,并且因为您没有在任何地方重复自己,您只需更改 GetPerson 方法中的代码。

      附:当您最终实现该通用存储库时,获取记录的代码可能看起来像这样。

      T e = Context.Set<T>().Find(id)

      一条线就能全部搞定

      【讨论】:

      • 对我来说,这里的延迟加载将是一个主要的性能问题。您正在显示数据,所以已经检索它! DRY 原则是一个原则,应该作为一个原则使用,而不是作为口头禅。您不能为对 Person 的每次调用构建一个方法,否则您将始终检索所有内容。如果您只需要一个子集怎么办?好吧,它不再那么 DRY... 最后,一个更好的方法是创建一个 PersonRepository(通用存储库模式),这将有助于保持事物的组织性并避免代码重复。
      • @LordALMMa 你在这里提出延迟加载的想法是正确的,但不是因为它是一个性能问题。也许您将我在此处展示的示例与 Entity Framework 的延迟加载混淆了?以我的经验,许多开发人员都在处理 ASP.NET 中的事件序列,并在一页的生命周期中多次访问数据库。当他们以这种方式编码时引入延迟加载是重构它的一种方法。但是,建议将其作为在 ASP.NET 中编程的标准方式是不合适的,我已根据您的 cmets 修改了我的答案
      • @LordALMMa 你又是对的! “口头禅:经常重复的陈述;一个典型的公式或副歌”。我怎么可能将它与 DRY 结合使用。现在把那些东西打出来! :-)
      • 对不起,但我没有让自己太清楚:通过性能问题,我指的是从您的示例中,您每次都会为每个人打一个电话。如果遍历 100 个实体,这将导致对数据库的 100 次调用。所以我的观点是:如果您已经在使用 People,请加载它们,不要延迟加载,否则会因每次加载单个属性而导致一些问题。关于 DRY,只是 DRY 更多的是一种指导或指导,而不是普遍的真理。您应该尽可能应用它。但有时您必须以 DRY x 复杂性为代价。
      • @LordALMMa “我正在……尝试检索要在 FormView 中显示和编辑的记录”。此页面上只需要有一条记录。由于代码中的错误而检查您尚未获取它是确保只有一个的一种方法。
      【解决方案4】:

      尝试使用

            xxx.xxx.SelectSingleOrDefault(c => c.AccountSenderID == userId_int)
      

      替换匿名 lambda 表达式的使用(例如使用 var)

                         xxx.xxx.Select(c => new { c.FriendInvitationID,c.AccountSenderID,
                              c.Account1.AccountID, c.Account1.FirstName, c.Account1.LastName, c.Account1.Email,
                              c.FriendInvitationStatus, c.CreationDate })
                          .Where(c => c.AccountSenderID == userId_int).ToList();
      

      你不必描述你的对象,即使匿名在这个意义上更具动态性(图像你想检索一个具有同一个表的两个不同引用的 json 对象,在这种情况下你必须声明字段,因为它们将具有相同的名称,不过)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-12-16
        • 2018-04-19
        • 1970-01-01
        • 1970-01-01
        • 2021-09-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多