• 《Entity Framework 6 Recipes》中文翻译系列 (41) ------ 第七章 使用对象服务之标识关系中使用依赖实体与异步查询保存


    翻译的初衷以及为什么选择《Entity Framework 6 Recipes》来学习,请看本系列开篇 

    7-7  标识关系中使用依赖实体

    问题

      你想在标识关系中插入,更新和删除一个依赖实体。

    解决方案

      假设你有如图7-8所示的模型。实体LineItem的实体键是一个复合键。由InvoiceNumber和ItemNumber复合而成。InvoiceNumber同是也是一个外键。

    图7-8. Invoie和LineItem是一个标识关系,这是因为实体LineItem的复合实体键

      当实体的一个属性,既是实体键又是外键时,就可以说这个实体在一个标识关系中。在我们的模型里,实体LineItem的实体键,它的标识,同时也是相对于实体Invoice的外键。实体LineItem被称作依赖实体(depaendent entity),Invoice被称作主实体(principal entity)。

      实体框架在处理如何删除标识关系中的依赖实体时,有所不同。因为依赖实体不能离开标识关系而存在。简单地从主实体的集合中删除依赖实体,在实体框架中的结果是,删除的依赖实体被标记为删除状态。另外,删除主实体,会连同依赖实体一直被标记为删除。这让我们想起数据库中级联删除。当然,实体框架还允许显式删除依赖实体。 代码清单7-5演示了这三种不同的场景。

    代码清单7-5. 删除依赖实体

     1  public static class Recipe7Program
     2     {
     3         public static void Run()
     4         {
     5             using (var context = new Recipe7Context())
     6             {
     7 
     8                 var invoice1 = new Invoice
     9                                    {
    10                                        BilledTo = "Julie Kerns",
    11                                        InvoiceDate = DateTime.Parse("9/19/2013")
    12                                    };
    13                 var invoice2 = new Invoice
    14                                    {
    15                                        BilledTo = "Jim Stevens",
    16                                        InvoiceDate = DateTime.Parse("9/21/2013")
    17                                    };
    18                 var invoice3 = new Invoice
    19                                    {
    20                                        BilledTo ="Juanita James",
    21                                        InvoiceDate = DateTime.Parse("9/23/2013")
    22                                    };
    23                 context.LineItems.Add(new LineItem
    24                                           {
    25                                               Cost = 99.29M,
    26                                               Invoice = invoice1
    27                                           });
    28                 context.LineItems.Add(new LineItem
    29                                           {
    30                                               Cost = 29.95M,
    31                                               Invoice = invoice1
    32                                           });
    33                 context.LineItems.Add(new LineItem
    34                                           {
    35                                               Cost = 109.95M,
    36                                               Invoice = invoice2
    37                                           });
    38                 context.LineItems.Add(new LineItem
    39                                           {
    40                                               Cost = 49.95M,
    41                                               Invoice = invoice3
    42                                           });
    43                 context.SaveChanges();
    44 
    45                 // 显示LineItems
    46                 Console.WriteLine("Original set of line items...");
    47                 DisplayLineItems();
    48 
    49                 //从invoice1的集合中移除一个LineItem
    50                 var item = invoice1.LineItems.ToList().First();
    51                 invoice1.LineItems.Remove(item);
    52                 context.SaveChanges();
    53                 Console.WriteLine("
    After removing a line item from an invoice...");
    54                 DisplayLineItems();
    55 
    56                 // 移除 invoice2
    57                 context.Invoices.Remove(invoice2);
    58                 context.SaveChanges();
    59                 Console.WriteLine("
    After removing an invoice...");
    60                 DisplayLineItems();
    61 
    62                 //单独移除一个LineItem
    63                 context.LineItems.Remove(invoice1.LineItems.First());
    64                 context.SaveChanges();
    65                 Console.WriteLine("
    After removing a line item...");
    66                 DisplayLineItems();
    67 
    68                 //单独更新一个LineItem
    69                 var item2 = invoice3.LineItems.ToList().First();
    70                 item2.Cost = 39.95M;
    71                 context.SaveChanges();
    72                 Console.WriteLine("
    After updating a line item from an invoice …");
    73                 DisplayLineItems();
    74             }
    75         }
    76 
    77         static void DisplayLineItems()
    78         {
    79             bool found = false;
    80             using (var context = new Recipe7Context())
    81             {
    82                 foreach (var lineitem in context.LineItems)
    83                 {
    84                     Console.WriteLine("Line item: Cost {0}", 
    85                                        lineitem.Cost.ToString("C"));
    86                     found = true;
    87                 }
    88             }
    89             if (!found)
    90                 Console.WriteLine("No line items found!");
    91         }
    92 
    93     }

    代码清单7-5的输出如下:

    Original set of line items...
    Line item: Cost $99.29
    Line item: Cost $29.95
    Line item: Cost $109.95
    Line item: Cost $49.95
    After removing a line item from an invoice...
    Line item: Cost $29.95
    Line item: Cost $109.95
    Line item: Cost $49.95
    After removing an invoice...
    Line item: Cost $29.95
    After removing a line item...
    Line item: Cost $49.95
    After updating a line item...
    Line item: Cost $39.95

    原理

      代码清单7-5使用三种方法删除LineItem。第一种从Invoice的集合中删除。因为一个LineItem依赖Invoice的标识,实体框架标记引用的LineItme对象为删除状态。第二种是删除invoice对象,实体框架会标记所有的依赖于此对象的lineItme为删除状态。最后一种,是直接调用Remove()方法从上下文对象中的LineItems实体集中删除最后剩下的一个lineItem对象。

      你可以修改依赖实体的,除了标识关系中的属性以外的所有属性。在我们的模型中,可以修改实体LineItem中的Cost属性。但不能修改Invoice导航属性。

      当我们保存一个标识关系中的主实体对象时,它的实体键值是从数据库中产生(由存储层产生值),并被写回到主实体对象,以及它的所有依赖实体中的。这得确保所有的操作在数据库上下文中都是同步进行的。

    7-8  使用数据库上下文插入实体

    问题

      你想使用数据上下文将模型中实体插入到数据库中。

    解决方案

      假设你有如图7-9所示的模型。

    图7-9 一个包含实体employee和task的模型

      图7-9中的模型表示员工和他们的任务。你想插入一个新的员工和它的任务到数据库中。为了插入一个员工,创建一个Employee的实例并调用上下文对象中Employees实体集中的方法Add()。为了添加员工的一个任务,创建一个Task的实例,并将它添加到employee对象的Tasks集合中。 你还必须调用Add()方法将employee和task添加到数据库上下文中。最后,调用SaveChanges()方法,将这些变化持久化到数据库中。

      代码清单7-6演示了,使用Add()方法添加新对象到上下文中,并调用SaveChanges()方法持久化到数据库中。

    代码清单7-6. 插入新实体到数据库

     1  public static class Recipe8Program
     2     {
     3         public static void Run()
     4         {
     5             using (var context = new Recipe8Context())
     6             {
     7                 var employee1 = new Employee
     8                 {
     9                     EmployeeNumber = 629,
    10                     Name = "Robin Rosen",
    11                     Salary = 106000M
    12                 };
    13                 var employee2 = new Employee
    14                 {
    15                     EmployeeNumber = 147,
    16                     Name = "Bill Moore",
    17                     Salary = 62500M
    18                 };
    19                 var task1 = new Task { Description = "Report 3rd Qtr Accounting" };
    20                 var task2 = new Task { Description = "Forecast 4th Qtr Sales" };
    21                 var task3 = new Task { Description = "Prepare Sales Tax Report" };
    22 
    23                 //在Employees实体集上使用Add()方法
    24                 context.Employees.Add(employee1);
    25 
    26                 //添加两个新的task到employee1的Tasks中
    27                 employee1.Tasks.Add(task1);
    28                 employee1.Tasks.Add(task2);
    29 
    30                 // 添加一个task到employee2的Tasks中,并使用Add()方法添加task到上下文中
    31                 employee2.Tasks.Add(task3);
    32                 context.Tasks.Add(task3);
    33 
    34                 //持久化到数据库
    35                 context.SaveChanges();
    36             }
    37 
    38             using (var context = new Recipe8Context())
    39             {
    40                 foreach (var employee in context.Employees)
    41                 {
    42                     Console.WriteLine("Employee: {0}'s Tasks", employee.Name);
    43                     foreach (var task in employee.Tasks)
    44                     {
    45                         Console.WriteLine("	{0}", task.Description);
    46                     }
    47                 }
    48             }
    49             
    50         }
    51     }

    代码清单7-6的输出如下:

    Employee: Bill Moore's Tasks
        Prepare Sales Tax Repor
    Employee: Robin Rosen's Tasks
        Report 3rd Qtr Accounti
        Forecast 4th Qtr Sales

    原理

      在代码清单7-6中,我们使用实体集Employees和Tasks中的Add()方法,添加实体到数据库上下文中。

      当你添加一个实体到上下文中,实体框架会为这个新加入的实体,创建一个临时实体键。 实体框架使用这个临时的键来唯一标识该实体。当实体被持久化到数据库后,这个临时的实体键,会被一个真正的键值给替换。如果添加到数据库中的两个实体被分配相同的实体键,实体框架会抛出一个异常。这种情况一般发生在客户端或者存储生成过程,给实体分配了相同的键值。

      对于外键关联,你可以使用关联实体的实体键分配给实体的外键属性。虽然涉及临时键值,但实体框架会在实体被保存到数据库后,正确地修正键和关系。

      你还可以使用Attach()方法来添加一个实体到上下文中。这是一个分为两步的过程,首先是调用Attach()方法。它将添加实体到上下文中,但是变化跟踪器一开始将实体标记为Unchanged状态。如果这时调用SaveChanges()方法,它不会将实体保存到数据库。第二步是,将实体递给上下文的Entry()方法,获得一个DBEntityEntry实例,并设置它的属性State为新的状态:EntityState.Added。这时调用SaveChanges()方法会将新实体保存到数据库。

    7-9  异步查询和保存

    问题

      你想在执行查询和持久化变更时,保持应用程序的响应性。

    解决方案

      假设你有POCO实体,Account和Transaction,你想使用Code-First建模,如代码清单7-7所示。

     1  public class Account
     2     {
     3         public int AccountNumber { get; set; }
     4         public string AccountHolder { get; set; }
     5 
     6         public virtual ICollection<Transaction> Transactions { get; set; } 
     7     }
     8 
     9     public class Transaction
    10     {
    11         public int AccountNumber { get; set; }
    12         public int TransactionNumber { get; set; }
    13         public DateTime TransactionDate { get; set; }
    14         public decimal Amount { get; set; }
    15     }

      实体Transaction显然是实体Account的依赖实体,所以我们通过创建一个EntityTypeConfiguration子类来为每个实体配置关系。如代码清单7-8所示。

    代码清单7-8. 配置实体Account和Transaction

     1  public class AccountTypeConfiguration : EntityTypeConfiguration<Account>
     2     {
     3         public AccountTypeConfiguration()
     4         {
     5             HasKey(a => a.AccountNumber);
     6 
     7             Property(a => a.AccountNumber)
     8                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
     9 
    10             HasMany(a => a.Transactions)
    11                 .WithRequired();
    12         }
    13     }
    14 
    15     public class TransactionTypeConfiguration : EntityTypeConfiguration<Transaction>
    16     {
    17         public TransactionTypeConfiguration()
    18         {
    19             HasKey(t => new {t.AccountNumber, t.TransactionNumber});
    20 
    21             Property(t => t.TransactionNumber)
    22                 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    23         }
    24     }

      最后,在代码清单7-9中,我们创建DbContext的子类并重写OnModelCreating方法,在这个方法中,我们添加实体配置到模型构建器的配置集合中。

    代码清单7-9. 创建DbContext的子类

     1  public class Recipe9Context : DbContext
     2     {
     3         public DbSet<Account> Accounts { get; set; }
     4         public DbSet<Transaction> Transactions { get; set; }
     5 
     6         public Recipe9Context() : base("name=EF6CodeFirstRecipesContext")
     7         {
     8             
     9         }
    10 
    11         protected override void OnModelCreating(DbModelBuilder modelBuilder)
    12         {
    13             base.OnModelCreating(modelBuilder);
    14 
    15             modelBuilder.Configurations.Add(new AccountTypeConfiguration());
    16             modelBuilder.Configurations.Add(new TransactionTypeConfiguration());
    17         }
    18     }

      为了异步查询和保存,我们将分别使用LINQ to Entities方法ForEachAsync(),和上下文DbContext的方法SaveChangesAsync()。代码清单7-10演示了这两个方法的用法。

    代码清单7-10. 异步查询和保存

      1  public static class Recipe9Program
      2     {
      3         public static async Task Run()
      4         {
      5             using (var context = new Recipe9Context())
      6             {
      7                 var account1 = new Account
      8                 {
      9                     AccountHolder = "Robert Dewey",
     10                     Transactions = new HashSet<Transaction>()
     11                                                           {
     12                                                               new Transaction
     13                                                                   {
     14                                                                       TransactionDate = Convert.ToDateTime("07/05/2013"),
     15                                                                       Amount = 104.00M
     16                                                                   },
     17                                                              new Transaction
     18                                                                  {
     19                                                                      TransactionDate = Convert.ToDateTime("07/12/2013"),
     20                                                                      Amount = 104.00M
     21                                                                  },
     22                                                              new Transaction
     23                                                                  {
     24                                                                      TransactionDate = Convert.ToDateTime("07/19/2013"),
     25                                                                      Amount = 104.00M
     26                                                                  }
     27                                                           }
     28                 };
     29                 var account2 = new Account
     30                 {
     31                     AccountHolder = "James Cheatham",
     32                     Transactions = new List<Transaction>
     33                                                           {
     34                                                               new Transaction
     35                                                                   {
     36                                                                       TransactionDate = Convert.ToDateTime("08/01/2013"),
     37                                                                       Amount = 900.00M
     38                                                                   },
     39                                                               new Transaction
     40                                                                   {
     41                                                                       TransactionDate = Convert.ToDateTime("08/02/2013"),
     42                                                                       Amount = -42.00M
     43                                                                   }
     44                                                           }
     45                 };
     46                 var account3 = new Account
     47                 {
     48                     AccountHolder = "Thurston Howe",
     49                     Transactions = new List<Transaction>
     50                                                           {
     51                                                               new Transaction
     52                                                                   {
     53                                                                       TransactionDate = Convert.ToDateTime("08/05/2013"),
     54                                                                       Amount = 100.00M
     55                                                                   }
     56                                                           }
     57                 };
     58 
     59                 context.Accounts.Add(account1);
     60                 context.Accounts.Add(account2);
     61                 context.Accounts.Add(account3);
     62                 context.SaveChanges();
     63 
     64                 //为每个account添加每月的服务费
     65                 foreach (var account in context.Accounts)
     66                 {
     67                     var transactions = new List<Transaction>
     68                                        {
     69                                            new Transaction
     70                                                {
     71                                                    TransactionDate = Convert.ToDateTime("08/09/2013"),
     72                                                    Amount = -5.00M
     73                                                },
     74                                            new Transaction
     75                                                {
     76                                                    TransactionDate = Convert.ToDateTime("08/09/2013"),
     77                                                    Amount = -2.00M
     78                                                }
     79                                        };
     80 
     81                     Task saveTask = SaveAccountTransactionsAsync(account.AccountNumber, transactions);
     82 
     83                     Console.WriteLine("Account Transactions for the account belonging to {0} (acct# {1})", account.AccountHolder, account.AccountNumber);
     84                     await saveTask;
     85                     await ShowAccountTransactionsAsync(account.AccountNumber);
     86                 }
     87 
     88 
     89             }
     90 
     91         }
     92 
     93         private static async Task SaveAccountTransactionsAsync(int accountNumber, ICollection<Transaction> transactions)
     94         {
     95             using (var context = new Recipe9Context())
     96             {
     97                 var account = new Account { AccountNumber = accountNumber };
     98                 context.Accounts.Attach(account);
     99                 context.Entry(account).Collection(a => a.Transactions).Load();
    100 
    101                 foreach (var transaction in transactions.OrderBy(t => t.TransactionDate))
    102                 {
    103                     account.Transactions.Add(transaction);
    104                 }
    105 
    106                 await context.SaveChangesAsync();
    107 
    108             }
    109 
    110         }
    111 
    112         private static async Task ShowAccountTransactionsAsync(int accountNumber)
    113         {
    114             Console.WriteLine("TxNumber	Date	Amount");
    115             using (var context = new Recipe9Context())
    116             {
    117                 var transactions = context.Transactions.Where(t => t.AccountNumber == accountNumber);
    118                 await transactions.ForEachAsync(t => Console.WriteLine("{0}	{1}	{2}", t.TransactionNumber, t.TransactionDate, t.Amount));
    119             }
    120         }
    121     }

    原理

      示例中使用的异步结构是.NET4.5引入的,用来降低平常编写异步代码的复杂性。当我们调用SaveAccountTransactionsAsync()方法时,我们将它分配给Task对象。它调用方法并向调用者返回执行权。同时,异步部分,SaveAccounTransactionsAsync()也在执行。调用ShowAccountTransactionsAsync()方法的代码与调用SaveAccountTransactionsAsync()方法类似。当等待两个方法的调用返回时,执行权返回到await语句的下一行代码。

      重要的是,要知道.NET4.5中的异步模型是单线程,不是多线程,所以,代码 await SaveAccountTransactionsAsync()会被挂起,直到这个方法返回。另外需要知道的是 ,任何方法在调用一个async方法时,它自己必须被async修改符标记,并且返回一个Task类型或者Task<T>类型。

      代码清单7-10的输出如下:

    Account Transactions for the account belonging to Robert Dewey (acct# 1)
    TxNumber Date Amount
    1 7/5/2013 12:00:00 AM 104.00
    2 7/12/2013 12:00:00 AM 104.00
    3 7/19/2013 12:00:00 AM 104.00
    7 8/9/2013 12:00:00 AM -5.00
    8 8/9/2013 12:00:00 AM -2.00
    Account Transactions for the account belonging to James Cheatham (acct# 2)
    TxNumber Date Amount
    4 8/1/2013 12:00:00 AM 900.00
    5 8/2/2013 12:00:00 AM -42.00
    9 8/9/2013 12:00:00 AM -5.00
    10 8/9/2013 12:00:00 AM -2.00
    Account Transactions for the account belonging to Thurston Howe (acct# 3)
    TxNumber Date Amount
    6 8/5/2013 12:00:00 AM 100.00
    11 8/9/2013 12:00:00 AM -5.00
    12 8/9/2013 12:00:00 AM -2.00

    至此,第七章结束,下篇我们开始第八章。感谢你的阅读。


     

    实体框架交流QQ群:  458326058,欢迎有兴趣的朋友加入一起交流

    谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

  • 相关阅读:
    对比使用Charles和Fiddler两个工具及利用Charles抓取https数据(App)
    Charles-安装和配置
    python算法-队列
    python算法-快速排序
    【Codeforces】383.DIV2
    static关键字
    UNIX环境高级编程--5
    【LeetCode】467. Unique Substrings in Wraparound String
    typedef关键字
    strcpy 和 memcpy自实现
  • 原文地址:https://www.cnblogs.com/VolcanoCloud/p/4544400.html
Copyright © 2020-2023  润新知