Entity Framework - 3. 基本操作 (Relationship)

[ 2009-03-27 15:53:39 | 作者: yuhen ]
字号: | |
EF 关联操作和 LINQ to SQL 类似,使用起来很简便。

uploads/200903/27_155348_snap1.png

[EdmEntityType(NamespaceName = "TestModel", Name = "User")]
public partial class User : EntityObject
{
    ...

    [EdmRelationshipNavigationProperty("TestModel", "UserGroup", "Group")]
    public EntityCollection<Group> Groups
    {
        get
        {
            return ((IEntityWithRelationships)(this)).RelationshipManager.
                GetRelatedCollection<Group>("TestModel.UserGroup", "Group");
        }
        set
        {
            if ((value != null))
            {
                ((IEntityWithRelationships)(this)).RelationshipManager.
                    InitializeRelatedCollection<Group>("TestModel.UserGroup", "Group", value);
            }
        }
    }
}

System.Data.Objects.DataClass.EntityCollection<TEntity> 为关联关系的 "Many" 端。
[EdmEntityType(NamespaceName = "TestModel", Name = "Order")]
public partial class Order : EntityObject
{
    ...

    [EdmRelationshipNavigationProperty("TestModel", "FK_Order_User", "User")]
    public User User
    {
        get
        {
            return ((IEntityWithRelationships)(this)).RelationshipManager.
                GetRelatedReference<User>("TestModel.FK_Order_User", "User").Value;
        }
        set
        {
            ((IEntityWithRelationships)(this)).RelationshipManager.
                GetRelatedReference<User>("TestModel.FK_Order_User", "User").Value = value;
        }
    }

    public EntityReference<User> UserReference
    {
        get
        {
            return ((IEntityWithRelationships)(this)).RelationshipManager.
                GetRelatedReference<User>("TestModel.FK_Order_User", "User");
        }
        set
        {
            if ((value != null))
            {
                ((IEntityWithRelationships)(this)).RelationshipManager.
                    InitializeRelatedReference<User>("TestModel.FK_Order_User", "User", value);
            }
        }
    }
}

System.Data.Objects.DataClass.System.EntityReference<TEntity> 为 "One or Zero" 端。注意, Order.User 其实是 Order.UserReference.Vaue 的一种 "快捷方式"。

1. Add
using (var context = new TestEntities())
{
    var user = context.Users.First(u => u.Name == "user1");

    // One to Many
    var order = new Order { Name = "Order3" };
    user.Orders.Add(order);

    // Many to Many
    var group2 = context.Groups.First(g => g.Name == "group2");
    user.Groups.Add(group2);

    // Save
    context.SaveChanges();
}

2. Load

EF 默认使用 Deferred Loading 机制,虽然同样是 "Lazy Loading",但和 LINQ to SQL 却有很大区别。LINQ to SQL 是一种 "Implicit Lazy Loading",当访问关联属性时,框架会自动在后台完成相应的查询和载入操作。而 EF 则是一种 "Explicit Lazy Loading",除非我们显式执行载入操作,否则框架并不会自动帮我们获取关联数据。

通过下面的演示,你会发现关联数据是 "空"。
// Many to One
using (var context1 = new TestEntities())
{
    var order = (from o in context1.Orders where o.User.Name == "user1" select o).FirstOrDefault();
    Console.WriteLine(order.User == null);
}

// Many to Many, LINQ to Entities
using (var context2 = new TestEntities())
{
    var q = from u in context2.Users select u;
    foreach (var user in q)
    {
        Console.WriteLine(user.Name);

        foreach (var group in user.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

// Many to Many, Entity SQL
using (var context3 = new TestEntities())
{
    var sql = "SELECT VALUE u FROM TestEntities.Users AS u";
    var users = context3.CreateQuery<User>(sql);

    foreach (var user in users)
    {
        Console.WriteLine(user.Name);

        foreach (var group in user.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

输出:
true

user1
user2

user1
user2

解决方法很简单,就是我们要在代码中明明白白告诉 EF,我要载入关联数据。下面给出了不同的 "载入" 方法。

对于 LINQ to Entities 而言,我们可以直接用匿名类型同时返回关联数据。
using (var context = new TestEntities())
{
    var q = from u in context.Users select new { User = u, Groups = u.Groups };

    foreach (var item in q)
    {
        var user = item.User;
        Console.WriteLine(user.Name);

        foreach (var group in user.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

另外一种做法就是显式调用 EntityReference<TEntity>.Load() 或 EntityCollection<TEntity>.Load()。
// One to Many
using (var context1 = new TestEntities())
{
    var user = (from u in context1.Users where u.Name == "user1" select u).FirstOrDefault();
    user.Orders.Load();
    foreach (var item in user.Orders)
    {
        Console.WriteLine(item.Name);
    }
}

// Many to One
using (var context2 = new TestEntities())
{
    var order = (from o in context2.Orders where o.User.Name == "user1" select o).FirstOrDefault();
    order.UserReference.Load();
    Console.WriteLine(order.User.Name);
}

// Many to Many
using (var context3 = new TestEntities())
{
    var users = from u in context3.Users select u;
    foreach (var item in users)
    {
        Console.WriteLine(item.Name);

        item.Groups.Load();
        foreach (var group in item.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

输出:
Order1
Order2
Order3

user1

user1
        Group1
        Group2
user2
        Group1

在循环中每次调用 Load 方法都会执行一次后台数据库查询操作,必然会导致一定的性能损失。我们可以改用 Eager Loading 模式来解决这个问题。
using (var context = new TestEntities())
{
    // var users = from u in context.Users.Include("Groups") where u.Groups.Count > 1 select u;

    var users = from u in context.Users.Include("Groups") select u;
    Console.WriteLine((users as ObjectQuery<User>).ToTraceString());

    foreach (var user in users)
    {
        Console.WriteLine(user.Name);

        foreach (var group in user.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

输出:
SELECT
[Project2].[Id] AS [Id],
[Project2].[Name] AS [Name],
[Project2].[Age] AS [Age],
[Project2].[C1] AS [C1],
[Project2].[C2] AS [C2],
[Project2].[Id1] AS [Id1],
[Project2].[Name1] AS [Name1]
FROM ( SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[Age] AS [Age],
    1 AS [C1],
    [Project1].[Id] AS [Id1],
    [Project1].[Name] AS [Name1],
    [Project1].[C1] AS [C2]
    FROM  [dbo].[User] AS [Extent1]
    LEFT OUTER JOIN  (SELECT
        [Extent2].[UserId] AS [UserId],
        [Extent3].[Id] AS [Id],
        [Extent3].[Name] AS [Name],
        1 AS [C1]
        FROM  [dbo].[UserGroup] AS [Extent2]
        INNER JOIN [dbo].[Group] AS [Extent3] ON [Extent3].[Id] = [Extent2].[GroupId] ) 
        AS [Project1] ON [Extent1].[Id] = [Project1].[UserId]
)  AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C2] ASC

Include() 是 ObjectQuery<T> 的方法,因此也可以用于 Entity SQL。
using (var context = new TestEntities())
{
    var sql = "SELECT VALUE u FROM TestEntities.Users AS u";
    var users = context.CreateQuery<User>(sql).Include("Groups");

    foreach (var user in users)
    {
        Console.WriteLine(user.Name);

        foreach (var group in user.Groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

除此之外,Entity SQL 本身也可以实现类似的功能。
using (var context = new TestEntities())
{
    var sql = "SELECT u, u.Groups FROM TestEntities.Users AS u";
    var reader = context.CreateQuery<IExtendedDataRecord>(sql);

    Console.WriteLine(reader.ToTraceString());

    foreach (var item in reader)
    {
        var user = item[0] as User;
        var groups = item[1] as IList<Group>;

        Console.WriteLine(user.Name);

        foreach (Group group in groups)
        {
            Console.WriteLine("\t{0}", group.Name);
        }
    }
}

某些时候使用 CreateQuery<DbDataReader>() 会出现如下异常,那么改成 IExtendedDataRecord 即可。

uploads/200903/27_155353_snap2.png


附: Include 并不适用所有场合,因为该操作可能会一次返回 "太多" 的记录,反而导致性能的降低。编码人员要根据具体的需求,合理使用 Include() 和 Load()。

3. Remove
using (var context = new TestEntities())
{
    var user = context.Users.
        Where("it.Name = @name", new ObjectParameter("name", "user1")).
        Include("Groups").
        FirstOrDefault();

    foreach (var group in user.Groups)
    {
        Console.WriteLine(group.Name);
    }

    var group2 = context.Groups.FirstOrDefault(g => g.Name == "group2");
    user.Groups.Remove(group2);
    context.SaveChanges();
}

4. Clear

EntityCollection<TEntity>.Clear() 循环调用 Remove() 来删除列表中的关联对象。
using (var context = new TestEntities())
{
    var user = context.Users.
        Where("it.Name = @name", new ObjectParameter("name", "user1")).
        Include("Groups").
        FirstOrDefault();

    user.Groups.Clear();
    context.SaveChanges();
}
[最后修改由 yuhen, 于 2009-03-28 09:51:56]
评论Feed 评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=791

这篇日志没有评论。

发表评论
表情图标
[smile] [confused] [cool] [cry]
[eek] [angry] [wink] [sweat]
[lol] [stun] [razz] [redface]
[rolleyes] [sad] [yes] [no]
[heart] [star] [music] [idea]
UBB代码
转换链接
表情图标
悄悄话
用户名:   密码:  
验证码 * 请输入验证码