zoukankan      html  css  js  c++  java
  • Pro LINQ 之五:LINQ to Entity

    引言

    《Pro LINQ in C# 2008》本没有LINQ to Entity的内容,有的只是LINQ to SQL Entity。前者是ADO.NET Entity Framework的核心组成,而后者则是LINQ to SQL的组成部分。前者已经有越来越多的数据库提供了相应的Provider支持LINQ查询和Entity生成,后者仍仅限于MS SQL Server。

    《Pro LINQ in C# 2010》是新近出版的。该书在保留了原2008版所有内容的基础上,主要增加了LINQ to Entity以及Parallel LINQ的内容。可是不知道什么原因,2010版在这两个非常重大的LINQ技术改进上,却没有用到应有的笔墨,实在令人遗憾。

    从个人感受讲,Pro系列的书大多只适合于从入门到中级,更深入的内容是无法从中获得的。所以,我最近有了一本O'Reilly出版的《Programming Entity Framework》,相信可以从中获得我需要的内容。

    我通常会从LINQ的Wiki获取最新的Provider列表:http://en.wikipedia.org/wiki/Language_Integrated_Query

    初识LINQ to Entity

    之前有了LINQ to SQL Entity,理解LINQ to Entity时便免不了对二者进行比较。LINQ to Entity被设计与所有支持ADO.NET的数据库进行交互,其大部分操作与LINQ to SQL Entity类似,类的映射关系也与之大部相似。但是,LINQ to Entity更复杂,也更具功能性,已经有了面向对象数据库的雏形。

    与SQL Entity比较,可以简单地对LINQ to Entity中作如下理解:ObjectsContext映射数据库,ObjectContext.Entity映射表,Entity映射行,Entity.Property映射列。对应于SQL Entity中由EntityRef<T>与EntitySet<T>建立的Relationship,在LINQ to Entity中有EntityReference<T>与EntityCollection<T>(其中T为子表对应Entity类)实现的Association。

    在Visual Studio 2010里,LINQ to SQL Entity是通过“LINQ to SQL类”(.dbml)生成,需要手工从数据库向设计器里拖放表、视图和存储过程等。LINQ to Entity则是通过“ADO.NET实体数据模型”(.edmx)生成,可以在向导里勾选需要的表、视图和存储过程等。对应单个的LINQ to Entity类,还有“ADO.NET自跟踪实体生成器”与“ADO.NET Entity Object生成器”。

    LINQ to Entity常见操作

    LINQ to Entity的常见CUD操作与SQL Entity极其类似,因此以下只是对书中没有明示的,或者自己有疑问的做了进一步的探索。

    SaveChanges()支持Entity对象的自动回滚吗?

    我用下面的一段测试代码,确定SubmitChanges()变成SaveChanges()后,同样没有支持Entity对象的回滚。

    #region 1: insert a row with dulplicate PK.
    Customer c = Customer.CreateCustomer("ALFKI", "My company");
    db.Customers.AddObject(c);
    try
    {
        db.SaveChanges();
        Console.WriteLine("insertion successes.");
    }
    catch (OptimisticConcurrencyException)
    { Console.WriteLine("conflict happens."); }
    catch (UpdateException)
    { Console.WriteLine("update exception happens."); }
    #endregion
    
    
    #region 2: modify the customer constructed.
    Console.WriteLine("modify id to AAAAA");
    c.CustomerID = "AAAAA";
    db.Customers.AddObject(c);
    try
    {
        db.SaveChanges();
        Console.WriteLine("insertion successes.");
    }
    catch (OptimisticConcurrencyException)
    { Console.WriteLine("conflict happens."); }
    catch (UpdateException)
    { Console.WriteLine("update exception happens."); }
    #endregion
    
    #region 3: delete the customer inserted before.
    db.Customers.DeleteObject(c);
    #endregion
    
    #region 4: construct a new customer to insert.
    Console.WriteLine("insert a new customer.");
    Customer c2 = Customer.CreateCustomer("AAAAA", "Test2");
    db.Customers.AddObject(c2);
    try
    {
        db.SaveChanges();
        Console.WriteLine("insertion successes.");
    }
    catch (OptimisticConcurrencyException)
    { Console.WriteLine("conflict happens."); }
    catch (UpdateException)
    { Console.WriteLine("update exception happens."); }
    #endregion

    上述3段代码,1会触发1次Update异常,1+2能成功添加,1+4会触发2次Update异常,1+3+4能成功添加。

    级联表的添加与删除

    对存在父子关系的两张表,在添加时尽量从父表的角度出发,这样即便要删除父表中的行,其关联的子表行也将被自动删除(没有预先设置级联却也可以,Why?)。反之,苦从子表角度出发,则需要显式地先删除子表的行,再删除父表中的行。

    编译后的LINQ查询(MSDN称其为“缓存的LINQ查询”)

    注意LINQ to Entity的编译后查询,需要引用的命名空间为System.Data.Objects,而不是System.Data.Linq。这两个空间内,都有对应的CompiledQuery类定义。此前,我一直使用同一个方案在学习LINQ,因此没有正确地引用LINQ to Entity的命名空间,导致我自己编写的编译后查询老是无法通过编译器检查。

    样式如下:

    Func<ObjectContext, 传入参数类型1~n, 返回值类型> 编译后查询名称或方法名

    = CompiledQuery.Compile<ObjectContext, 传入参数类型1~n, 返回值类型>

    ((context, 传入参数1~n) => LINQ查询语句);

    查看LINQ to Entity生成的SQL语句

    ObjectContext没有再象DataContext一样暴露Log属性,供客户查阅LINQ生成的SQL查询语句。因此只有变相地使用下面这样的方法,通过ObjectQuery.ToTraceString()获得该查询语句。

    IQueryable<Customer> londonCustomers = from c in db.Customers
                                            where c.City == "LONDON"
                                            select c;
    // ensure that the database connection is open
    if (db.Connection.State != ConnectionState.Open)
    {
        db.Connection.Open();
    }
    // display the sql statement
    string sqlStatement = (londonCustomers as ObjectQuery).ToTraceString();

    关联数据的加载

    类似于LINQ to SQL中使用DataContext.LoadWith()强制地改变关联表的加载时机,在LINQ to Entity里,为每一个Entity类提供了一个Include()方法,用以强制加载其子表。这个Include()可以被放进编译后的LINQ本义定义中。其利弊如下:

    利:只取需要的数据,避免无谓的数据加载。

    弊:一旦要引用子表数据,将会为子表中的每一行数据引用生成一条SQL查询语句,从而影响查询效率。

    在使用Include()过程中,要特别注意以下几点:

    1. 如果设置了ObjectContext.ContextOptions.LazyLoadingEnabled = false; 则当你引用未被加载的子表数据时将会引发异常。

    2. Include()的参数为父表对象中子表对应的Property名称字符串,比如Customer中的"Orders",而不是子表对象或者其他什么东西。

    3. 如果要指定只加载子表中的特定行,则采取类似下述的方法进行显式的加载。其中的关键在于先置ObjectContext的LazyLoadingEnabled为false,再合理地使用条件判断与Orders.Load()方法、Orders.IsLoaded属性配合。

    IQueryable<Customer> custs = db.Customers
                    .Where(c => c.Country == "UK" && c.City == "London")
                    .OrderBy(c => c.CustomerID)
                    .Select(c => c);
    // explicitly load the orders for each customer
    foreach (Customer c in custs)
    {
        if (c.CompanyName != "North/South")
        {
            c.Orders.Load();
        }
    }
    
    foreach (Customer c in custs)
    {
        Console.WriteLine("{0} - {1}", c.CompanyName, c.ContactName);
        // check to see that the order data is loaded for this customerif (cust.Orders.IsLoaded) {
        if (c.Orders.IsLoaded)
        {
            Order firstOrder = c.Orders.First();
            Console.WriteLine(" {0}", firstOrder.OrderID);
        }
        else
        {
            Console.WriteLine(" No order data loaded");
        }
    }

    使用存储过程

    打开*.edmx文件,才能打开实体数据模型浏览器窗口。打开后,主要有两个分支。其中的xxxxxModel是生成的Entity模型,xxxxxModel.Store对应的数据库。

    选择存储过程->定义Entity环境下的方法名->取得存储过程各列信息->创建新的复杂类型->为新的复杂类型定义名称,这是将存储过程导入Entity模型的基本步骤。

    删除关联的Entity对象

    当删除父表中一行时,由于存在子表外键约束,会触发异常。为此,通常的做法是先删除所有的子表关联行,再删除父表中的行,最后再通过SaveChanges()提交给数据库。为了简化方法,LINQ to Entity提供了级联删除的功能。通过在实体模型浏览器中,先选择数据库中的父类,然后在其Keys中选择对应子表外键FK,设置Delete Rule为Cascade。然后再选择ORM中的关联(Association),找到对应的FK约束,同样设置OnDelete属性为Cascade。

    LINQ to Entity的冲突处理

    LINQ to Entity仍旧采用了开放式的并发模式,而且相对于P557中SQL Entity的并发冲突检测与处理机制而言,有所简化,核心就是决定是Client或者Database“胜出”。

    至于造成冲突的方式,仍旧沿用了在SQL Entity中采取的办法:先用Entity读取数据->用ADO.NET修改数据->修改Entity当前数据->经由LINQ to Entity提交给数据库。

    但与LINQ to SQL不同的是,Entity的冲突检测不再是设置Column的IsVersionColumn与UpdateCheck特性,而是在对象模型浏览器里,在对应Column的Entity特定Property上设置其Concurrency Mode为Fixed(默认为None)。

    ObjectContext仍是处理并发冲突的主体,类似于DataContext.Resove(),通过为其方法Refresh()提供恰当的RefreshMode,以及要被刷新的对象,即可实现冲突处理。

    RefreshMode.StoreWins

    最终Entity的值被刷新,数据库胜出。

    RefreshMode.ClientWins

    最终数据库值被刷新,Entity胜出。

    作者在P721提供了一个反复提交更新的结构,挺有趣的:

    int maxAttempts = 5;
    bool recordsUpdated = false;
    for (int i = 0; i < maxAttempts && !recordsUpdated; i++)
    {
        Console.WriteLine("Performing write attempt {0}", i);
        // save the changes
        try
        {
            context.SaveChanges();
            recordsUpdated = true;
        }
        catch (OptimisticConcurrencyException)
        {
            Console.WriteLine("Detected concurrency conflict - refreshing data");
            context.Refresh(RefreshMode.ClientWins, cust);
        }
    }

    LINQ to Entity与LINQ to SQL Entity的区别

    差异主要是因为比较器无法被转换为数据源,导致使用IEqualityComparer、IComparer接口的比较子的运算符不被LINQ to Entity支持。(对如何自定义Entity比较子,我将在O'Reilly的那本书中去寻求答案。)

    参见MSDN:支持和不支持的LINQ方法

    一切的一切,留待《Programming Entity Framework》解决……


    [LINQ] Pro LINQ 之六:并行LINQ查询

  • 相关阅读:
    multi-task learning
    代码杂谈-python函数
    代码杂谈-or符号
    安装maven
    zsh
    mint linux的几个问题
    [软件] Omnigraffle
    无梯度优化算法
    根据pdf文件获取标题等信息
    计算广告-GD广告
  • 原文地址:https://www.cnblogs.com/Abbey/p/2122780.html
Copyright © 2011-2022 走看看