老赵点滴


  先做人,再做技术人员,最后做程序员。
  我的理想:“让外国人看中国人写的技术书籍和文章”。Try as I might
posts - 287, comments - 10551, trackbacks - 137, articles - 6
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

  目前LINQ to SQL的资料不多——老赵的意思是,目前能找到的资料都难以摆脱“官方用法”的“阴影”。LINQ to SQL最权威的资料自然是MSDN,但是MSDN中的文档说明和实例总是显得“大开大阖”,依旧有清晰的“官方”烙印——这简直是一定的。不过从按照过往的经验,在某些时候如果不按照微软划定的道道来走,可能就会发现别样的风景。老赵在最近的项目中使用了LINQ to SQL作为数据层的基础,在LINQ to SQL开发方面积累了一定经验,也总结出了一些官方文档上并未提及的有用做法,特此和大家分享。

  言归正传,我们先看一个简单的例子。

1.jpg

  Item实体对应Item表,每个Item拥有一些评论,也就是ItemComment。Item实体中有一个Comments属性,是ItemComment实体的集合。这个例子将会使用这个再简单不过的模型。

  为用户显示他的Item列表是非常常见的需求,如果使用LINQ to SQL来获取Item的话,我们可能会这么做:

public List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.UserID == ownerId
                orderby item.CreateTime descending
                select item;
 
    return query.ToList();
}

  这么做自然可以实现我们想要的功能,这的确没错。但是这种做法有个很常见的问题,那就是可能会获得太多不需要的数据。一个Item数据量最大的是Introduction字段,而显示列表的时候我们是不需要显示它的。如果我们在获取Item列表时把Introduction一起获得的话,那么应用服务器和数据库服务器之间的数据通信量将会成百甚至上千地增长了。因此我们在面向此类需求的话,都会忽略每个Item对象的Introduction字段。那么我们该怎么做呢?对LINQ有简单了解的朋友们可能会想到这么做:

public List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.UserID == ownerId
                orderby item.CreateTime descending
                select new Item
                {
                    ItemID = item.ItemID,
                    Title = item.Title,
                    UserID = item.UserID,
                    CreateTime = item.CreateTime
                };

 
    return query.ToList();
}

  这个做法很直观,利用了C# 3.0中的Object Initializer特性。编译通过了,理应没有错,可是在运行时却抛出了NotSupportedException:“Explicit construction of entity type 'Demo.Item' in query is not allowed.”,意思就是不能在LINQ to SQL中显式构造Demo.Item对象。

  事实上在RTM之前的版本中,以上的语句是能运行通过的——我是指通过,不是正确。LINQ to SQL在RTM之前的版本有个Bug,如果在查询中显式构造一个实体的话,在某些情况下会得到一系列完全相同的对象。很可惜这个Bug我只在资料中看到过,而在RTM版本的LINQ to SQL中这个Bug已经被修补了,确切地说是绕过了。直接抛出异常不失为一种“解决问题”的办法,虽然这实际上是去除了一个功能——没有功能自然不会有Bug,就像没有头就不会头痛了一个道理。

  但是我们还得做,难道我们只能自己SQL语句了吗?

使用Translate方法

  幸亏DataContext提供了Translate方法,Translate方法的作用就是从一个DbDataReader对象中生成一系列的实例。其中最重要的就是一个带范型的重载:

public static List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    dataContext.Connection.Open();
 
    SqlCommand command = new SqlCommand(
        "SELECT [ItemID], [Title], [UserID], [CreateTime]" +
        " FROM [Item] WHERE [UserID] = " + ownerId +
        " ORDER BY [CreateTime]",
        (SqlConnection)dataContext.Connection);
 
    using (DbDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
    {
        return dataContext.Translate<Item>(reader).ToList();
    }
}

  在这段代码里,我们拼接出了一段SQL语句,实现了我们需要的逻辑。在ExecuteReader之后即使用dataContext.Translate方法将DbDataReader里的数据转换成Item对象。使用Translate方法除了方便之外,生成的对象也会自动Attach到DataContext中,也就是说,我们可以继续对获得的对象进行操作,例如访问Item对象的Comments属性时会自动去数据库获取数据,改变对象属性之后调用SubmitChange也能将修改提交至数据库。Translate方法从DbDataReader中生成对象的规则和内置的DataContext.ExecuteQuery方法一样,大家可以查看MSDN中的说明(中文英文)。

  此外,这里有两个细节值得一提:

  • 为什么调用ExecuteReader方法时要传入CommandBehavior.CloseConnection:LINQ to SQL中的DataContext对象有个特点,如果在使用时它的Connection对象被“显式”地打开了,即使调用了DataContext对象的Dispose方法也不会自动关闭。因此我们在开发程序的时候一定要注意这一点。例如,在调用ExecuteReader是传入CommandBehavior.CloseConnection,这样就保证了在关闭DbDataReader时同时关闭Connection——当然,我们也可以不这么做。
  • 在调用Translate方法后为什么要直接调用ToList方法:因为GetItemsForListing方法的返回值是List<Item>,这是原因之一。另一个原因是Translate方法并不会直接生成所有的对象,而是在外部代码访问Translate方法返回的IEnmuerable<T>时才会生成其中每个对象。这也是一种Lasy Load,但是也导致了所有的对象必须在Reader对象关闭之前生成,所以我一般都会在Translate方法后直接调用ToList方法,保证所有的对象已经生成了。虽然事实上我们也可以不使用using关键字而直接返回Translate方法生成的IEnumerable<Item>,不过这么做的话当前链接就得不到释放(释放,而不是关闭),也就是把处理数据连接的问题交给了方法的使用者——很可能就是业务逻辑层。为了确保分层结构的职责分明,我一般倾向于在这里确保所有对象的已经生成了。

  上面的例子使用拼接SQL字符串的方式来访问数据库,那我们又该如何使用LINQ to SQL呢?幸亏LINQ to SQL中的DataContext提供了GetCommand方法。我们直接来看一个完整的扩展:

public static class DataContextExtensions
{
    public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query)
    {
        DbCommand command = dataContext.GetCommand(query);
        dataContext.OpenConnection();
 
        using (DbDataReader reader = command.ExecuteReader())
        {
            return dataContext.Translate<T>(reader).ToList();
        }
    }
 
    private static void OpenConnection(this DataContext dataContext)
    {
        if (dataContext.Connection.State == ConnectionState.Closed)
        {
            dataContext.Connection.Open();
        }
    }
}

  自从有了C# 3.0中的Extension Method,很多扩展都会显得非常优雅,我非常喜欢这个特性。DataContextExtensions是我对于LINQ to SQL中DataContext对象的扩展,如果以后有新的扩展也会写在这个类中。OpenConnection方法用于打开DataContext中的数据连接,今后的例子中也会经常看到这个方法。而这次扩展的关键在于新的ExecuteQuery方法,它接受一个IQueryable类型的对象作为参数,返回一个范型的List。方法中会使用DataContext的GetCommand方法来获得一个DbCommand。在我之前的文章,以及MSDN中的示例都只是通过这个DbCommand对象来查看LINQ to SQL所生成的查询语句。也就是说以前我们用它进行Trace和Log,而我们这次将要真正地执行这个DbCommand了。剩下的自不必说,调用ExecuteReader方法获得一个DbDataReader对象,再通过Translate方法生成一个对象列表。

  新的ExecuteQuery方法很容易使用:

public static List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.UserID == ownerId
                orderby item.CreateTime descending
                select new
                {
                    ItemID = item.ItemID,
                    Title = item.Title,
                    CreateTime = item.CreateTime,
                    UserID = item.UserID
                };
 
    using (dataContext.Connection)
    {
        return dataContext.ExecuteQuery<Item>(query);
    }
}

  在通过LINQ to SQL获得一个query之后,我们不再直接获得查询数据了,而是将其交给我们的ExecuteQuery扩展来执行。现在这种做法既保证了使用LINQ to SQL进行查询,又构造出Item对象的部分字段,算是一种较为理想的解决方案。不过使用这个方法来获得仅有部分字段的对象时需要注意一点:在构造匿名对象时使用的属性名,可能和目标实体对象(例如之前的Item)的属性名并非一一对应的关系。

  这种情况会在实体对象的属性名与数据表字段名不同的时候发生。在使用LINQ to SQL时默认生成的实体对象,其属性名与数据库的字段名完全对应,这自然是最理想的情况。但是有些时候我们的实体对象属性名和数据库字段名不同,这就需要在ColumnAttribute标记中设置Name参数了(当然,如果使用XmlMappingSource的话也可以设置),如下:

[Table(Name = "dbo.Item")]
public partial class Item : INotifyPropertyChanging, INotifyPropertyChanged
{
    [Column(Storage = "_OwnerID", DbType = "Int NOT NULL", Name = "UserID")]
    public int OwnerID
    {
        get {...}
        set {...}
    }
}

  OwnerID属性上标记的ColumnAttribute的Name属性设为UserID,这表示它将与Item表中的UserID字段对应。那么如果我们要在这种情况下改写之前的GetItemsForListing方法,我们该怎么做呢?可能有朋友会很自然的想到:

public static List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.OwnerID == ownerId
                orderby item.CreateTime descending
                select new
                {
                    ItemID = item.ItemID,
                    Title = item.Title,
                    CreateTime = item.CreateTime,
                    OwnerID = item.OwnerID
                };
 
    using (dataContext.Connection)
    {
        return dataContext.ExecuteQuery<Item>(query);
    }
}

  按照“常理”判断,似乎只要将所有的UserID改为OwnerID即可——其实不然。查看方法返回的结果就能知道,所有对象的OwnerID的值都是默认值“0”,这是怎么回事呢?使用SQL Profiler观察以上代码所执行SQL语句之后我们便可明白一切:

SELECT [t0].[ItemID], [t0].[Title], [t0].[CreateTime], [t0].[UserID] AS [OwnerID]
FROM [dbo].[Item] AS [t0]
WHERE [t0].[UserID] = @p0
ORDER BY [t0].[CreateTime] DESC

  由于我们所使用的query实际上是用于生成一系列匿名对象的,而这些匿名对象所包含的是“OwnerID”而不是“UserID”,因此LINQ to SQL实际在生成SQL语句的时候会将UserID字段名转换成OwnerID。由于Item的OwnerID上标记的ColumnAttribute把Name设置成了UserID,所以Translate方法读取DbDataReader对象时事实上会去寻找UserID字段而不是OwnerID字段——这很显然就造成了目前的问题。因此,如果您使用了ColumnAttribute中的Name属性改变了数据库字段名与实体对象属性名的映射关系,那么在创建匿名对象的时候还是要使用数据库的字段名,而不是实体对象名,如下:

public static List<Item> GetItemsForListing(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.OwnerID == ownerId
                orderby item.CreateTime descending
                select new
                {
                    ItemID = item.ItemID,
                    Title = item.Title,
                    CreateTime = item.CreateTime,
                    UserID = item.OwnerID
                };
 
    using (dataContext.Connection)
    {
        return dataContext.ExecuteQuery<Item>(query);
    }
}

  这样就能解决问题了——不过显得不很漂亮,因此在使用LINQ to SQL时,我建议保持实体对象属性名与数据库字段名之间的映射关系。

改变LINQ to SQL所执行的SQL语句

  按照一般的做法我们很难改变LINQ to SQL查询所执行的SQL语句,但是既然我们能够将一个query转化为DbCommand对象,我们自然可以在执行之前改变它的CommandText。我这里通过一个比较常用的功能来进行演示。

  数据库事务会带来锁,锁会降低数据库并发性,在某些“不巧”的情况下还会造成死锁。对于一些查询语句,我们完全可以显式为SELECT语句添加WITH (NOLOCK)选项来避免发出共享锁。因此我们现在扩展刚才的ExecuteQuery方法,使它接受一个withNoLock参数,表明是否需要为SELECT添加WITH (NOLOCK)选项。请看示例:

public static class DataContextExtensions
{
    public static List<T> ExecuteQuery<T>(
        this DataContext dataContext, IQueryable query, bool withNoLock)
    {
        DbCommand command = dataContext.GetCommand(query, withNoLock);
 
        dataContext.OpenConnection();
 
        using (DbDataReader reader = command.ExecuteReader())
        {
            return dataContext.Translate<T>(reader).ToList();
        }
    }
 
    private static Regex s_withNoLockRegex =
        new Regex(@"(] AS \[t\d+\])", RegexOptions.Compiled);
 
    private static string AddWithNoLock(string cmdText)
    {
        IEnumerable<Match> matches =
            s_withNoLockRegex.Matches(cmdText).Cast<Match>()
            .OrderByDescending(m => m.Index);
        foreach (Match m in matches)
        {
            int splitIndex = m.Index + m.Value.Length;
            cmdText =
                cmdText.Substring(0, splitIndex) + " WITH (NOLOCK)" +
                cmdText.Substring(splitIndex);
        }
 
        return cmdText;
    }
 
    private static SqlCommand GetCommand(
        this DataContext dataContext, IQueryable query, bool withNoLock)
    {
        SqlCommand command = (SqlCommand)dataContext.GetCommand(query);
 
        if (withNoLock)
        {
            command.CommandText = AddWithNoLock(command.CommandText);
        }
 
        return command;
    }
}

  上面这段逻辑的关键在于使用正则表达式查找需要添加WITH (NOLOCK)选项的位置。在这里我查找SQL语句中类似“] AS [t0]”的字符串,并且在其之后添加WITH (NOLOCK)选项。其他的代码大家应该完全能够看懂,我在这里就不多作解释了。我们直接来看一下使用示例:

public static List<Item> GetItemsForListingWithNoLock(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
    var query = from item in dataContext.Items
                where item.UserID == ownerId
                orderby item.CreateTime descending
                select new
                {
                    ItemID = item.ItemID,
                    Title = item.Title,
                    CreateTime = item.CreateTime,
                    UserID = item.UserID
                };
 
    using (dataContext.Connection)
    {
        return dataContext.ExecuteQuery<Item>(query, true);
    }
}

  使用SQL Profiler查看上述代码所执行的SQL语句,就会发现:

SELECT [t0].[ItemID], [t0].[Title], [t0].[CreateTime], [t0].[UserID]
FROM [dbo].[Item] AS [t0] WITH (NOLOCK)
WHERE [t0].[UserID] = @p0
ORDER BY [t0].[CreateTime] DESC

  很漂亮。事实上只要我们需要,就可以在DbCommand对象生成的SQL语句上作任何修改(例如添加事务操作,容错代码等等),只要其执行出来的结果保持不变即可(事实上变又如何,如果您真有自己巧妙设计的话,呵呵)。

以上扩展所受限制

  以上的扩展并非无可挑剔。由于Translate方法的特点,此类做法都无法充分发挥LINQ to SQL查询的所有能力——那就是所谓的“LoadWith”能力。

  在LINQ to SQL中,默认会使用延迟加载,然后在必要的时候才会再去数据库进行查询。这个做法有时候会降低系统性能,例如:

List<Item> itemList = GetItems(1);
foreach (Item item in itemList)
{
    foreach (ItemComment comment in item.Comments)
    {
        Console.WriteLine(comment.Content);
    }
}

  这种做法的性能很低,因为默认情况下每个Item对象的ItemComment集合不会被同时查询出来,而是会等到内层的foreach循环执行时再次查询数据库。为了避免不合适的Lazy Load降低性能,LINQ to SQL提供了DataLoadOptions机制进行控制:

public static List<Item> GetItems(int ownerId)
{
    ItemDataContext dataContext = new ItemDataContext();
 
    DataLoadOptions loadOptions = new DataLoadOptions();
    loadOptions.LoadWith<Item>(item => item.Comments);
    dataContext.LoadOptions = loadOptions;
 
    var query = from item in dataContext.Items
                where item.UserID == ownerId
                orderby item.CreateTime descending
                select item;
 
    return query.ToList();
}

  当我们为DataContext对象设置了LoadOptions并且指明了“Load With”关系,LINQ to SQL就会根据要求查询数据库——在上面的例子中,它将生成如下的SQL语句:

SELECT [t0].[ItemID], [t0].[Title], [t0].[Introduction], [t0].[UserID], [t0].[CreateTime], [t1].[ItemCommentID], [t1].[ItemID] AS [ItemID2], [t1].[Content], [t1].[UserID], [t1].[CreateTime] AS [CreateTime2], (
    SELECT COUNT(*)
    FROM [dbo].[ItemComment] AS [t2]
    WHERE [t2].[ItemID] = [t0].[ItemID]
    ) AS [value]
FROM [dbo].[Item] AS [t0]
LEFT OUTER JOIN [dbo].[ItemComment] AS [t1] ON [t1].[ItemID] = [t0].[ItemID]
WHERE [t0].[UserID] = @p0
ORDER BY [t0].[CreateTime] DESC, [t0].[ItemID], [t1].[ItemCommentID]

  相信大家已经了解Translate方法为何无法充分发挥LINQ to SQL的能力了。那么我们又该如何解决这个问题呢?如果您希望同时使用本文类似的扩展和Load With能力,可能就需要通过查询两次数据库并加以组合的方式来生成对象了——虽然查询了两次,但总比查询100次的性能要高。

Tag标签: LINQ to SQL

Feedback

#1楼    回复  引用  查看    

2008-02-19 03:50 by 坏人      
很实用的技巧.

#2楼    回复  引用  查看    

2008-02-19 07:13 by carysun      
我看msdn总觉得他的资料很分散
觉得该放到一起的东西,他放在不同的类别里

#3楼    回复  引用    

2008-02-19 08:21 by eidolon [未注册用户]
实际上, 如果业务逻辑层返回IQueryable<T>到前端的话就可以解决很多的问题, 但是这样对客户端的调用要求稍微高一点, 另外就是分层方面不那么优雅.

#4楼    回复  引用  查看    

2008-02-19 08:32 by 驿路梨花      
老赵都已经在项目中使用VS2008了呀!太快了呀。

#5楼 [楼主]   回复  引用  查看    

2008-02-19 09:23 by Jeffrey Zhao      
@eidolon
如果把IQueryable释放到上层会有什么额外的好处呢?

#6楼 [楼主]   回复  引用  查看    

2008-02-19 09:23 by Jeffrey Zhao      
@驿路梨花
我2005和2008都是beta时就用起的,而且第一时间用于产品开发,呵呵。

#7楼    回复  引用    

2008-02-19 09:56 by eidolon [未注册用户]
就像你说的, 很多情况下你需要的List<Item>并不需要包含Item的所有字段信息, 为了达到最优化, 你可能只会选择必须的字段返回, 首先当然它有一定的局限性, 客户端不知道Item究竟哪些property被初始化了, 第二个就是如果针对包含不同字段的列表要吗你提供不同的方法, 要吗还是要返回包含覆盖全部字段的列表集合. 而如果返回IQueryable<T>的话, 这个是完全可以复用的, 客户端需要哪些字段就绑定哪些字段, 执行的时候SQL也只会选择这些字段, 不会有多余的字段被选择. 第二个问题就是排序相对方便一些, 而且可以是强类型的排序, 当然这个也可以封装在你的商业方法里面, 但是不可避免的要做一些转换, 要吗动态执行t-sql, 要吗需要动态执行LINQ, 但我觉得这样的代码放在客户端可能更容易理解一些:
if(条件成立)
q = q.OrderBy(u => u.UserID);
else
q = q.OrderByDescending(u => u.UserID);

ControlID.DataSource = q;
ControlID.DataBind();

#8楼 [楼主]   回复  引用  查看    

2008-02-19 10:15 by Jeffrey Zhao      
@eidolon
这个思路似乎不错,让我思考一下。:)
如果LINQ to SQL的IQueryable真能担负起如此重任应该还不错,但是我觉得如果这样的话,上层也就要知道下层(LINQ to SQL)的一些细节了,比如LoadOptions,否则可能会给系统带来麻烦。

#9楼    回复  引用    

2008-02-19 10:37 by eidolon [未注册用户]
@Jeffrey Zhao
的确, IQueryable<T>提供了太大的灵活性以至于可能产生一些问题, 你说的LoadOptions的确是一个很大的问题, 因为中间层无法控制, 如果前端开发人员不了解一点LINQ to SQL的话, 根本是不会去注意性能方面的影响的. 目前实际上我也是在IQueryable<T>和List<T>之间徘徊, 暂时也没有看到一个很好的方案能够完全解决所有的问题.

#10楼    回复  引用  查看    

2008-02-19 11:05 by lovecherry      
我不在逻辑层直接采用自动生成的实体,而是在数据层通过LINQ TO SQL转化为逻辑实体(逻辑实体可能和数据层的实体差别比较大,1是避免传输和从数据库中获取比必要的数据,2是为表现层的显示进行优化)

#11楼    回复  引用    

2008-02-19 11:41 by eidolon [未注册用户]
@lovecherry
如果你自定义DTO的话, 对于select之类的操作自然是ok, 如果对于insert, update的话呢? 也是一样初始化DTO, 然后传入中间层之后再转化为LINQ to SQL的实体然后再调用SubmitChanges()吗?

#12楼    回复  引用  查看    

2008-02-19 11:56 by 一瞬间      
老赵,我觉得第一个Query错误的原因可能是因为你没有定义的Item实体类吧,如果要用select Item的话你必须要声明一个public class Item 来和你的实体进行映射吧,假如你和后面一样用select new 的话应该不会有这样的错误吧?
还有不太清楚你所说的DataLoadOptions机制是什么意思呢 :)

#13楼    回复  引用  查看    

2008-02-19 12:52 by 无常      
博主的“个人信息”和“我的衣橱”几栏是怎么加上去的?
我怎么找不到

#14楼 [楼主]   回复  引用  查看    

2008-02-19 13:18 by Jeffrey Zhao      
@lovecherry
这么做感觉很古怪,如果要方便的话,我会选择扩展自动生成的实体类,比如添加新的成员封装自动生成的成员——这也是我经常做的。

#15楼 [楼主]   回复  引用  查看    

2008-02-19 13:19 by Jeffrey Zhao      
@一瞬间
Item是LINQ to SQL自动生成的(代码里有阿)。你尝试一下就知道了,是LINQ to SQL的一个“特点”,如果使用了new Item,编译能成功但是运行会失败,估计你没有看清楚文章内容。:)
至于DataLoadOptions,你可以看一下DataLoadOptions类,以及DataContext的LoadOptions属性。

#16楼 [楼主]   回复  引用  查看    

2008-02-19 13:30 by Jeffrey Zhao      
@无常
自己定制,系统没有提供这个功能。

#17楼    回复  引用  查看    

2008-02-19 14:29 by 一瞬间      
谢谢老赵,select Item笔误吧应该是select new Item
另外,有个问题,就是Loadwith,为什么后面设置了Loadwith以后就可以直接toList了,这里是怎么回事?

#18楼 [楼主]   回复  引用  查看    

2008-02-19 15:21 by Jeffrey Zhao      
@一瞬间
多谢提醒已经改正。
LoadWith后直接ToList是什么意思?

#19楼    回复  引用  查看    

2008-02-19 16:49 by 一瞬间      
@ Jeffrey zhao
这个return query.ToList();和前面的只是更改了LoadOption而已。

#20楼 [楼主]   回复  引用  查看    

2008-02-19 17:25 by Jeffrey Zhao      
@一瞬间
直接ToList就立即执行了,LoadOptions是指定某个对象加载时“同时”加载哪些对象,两者没有联系。

#21楼    回复  引用  查看    

2008-02-19 19:27 by Tristan(Guozhijian)      
这么做感觉很古怪,如果要方便的话,我会选择扩展自动生成的实体类,比如添加新的成员封装自动生成的成员——这也是我经常做的。
@Jeffrey Zhao

我也比较倾向这种做法。

#22楼    回复  引用  查看    

2008-02-20 22:02 by Cat Chen      
非常非常漂亮!我觉得在RTM之前,那个bug很符合一句话:It's not a bug; It's a feature!

#23楼    回复  引用  查看    

2008-02-20 22:10 by Cat Chen      
不过,反观Linq to Sql要如此复杂才能完成上述几个功能,其中一部分Rails轻松能做到,这也算是Linq to Sql搞得过于复杂的地方。

在Rails中,要选择特定的列,Model不需要改变,在find()方法中给出你要的列就是了,不用的列自然不会填充。如果需要在SQL语句最后追加指令,例如"WITH(NOLOCK)"?也是直接在find()方法里面增加参数就可以了。

#24楼 [楼主]   回复  引用  查看    

2008-02-20 22:40 by Jeffrey Zhao      
@Cat Chen
WITH (NOLOCK)是修改,规则可能很复杂,rails里怎么追加参数呢?

#25楼    回复  引用  查看    

2008-02-21 10:01 by 光影传说      
@Jeffrey Zhao
老赵,我是非常喜欢返回List这种类型,这样的后续处理好像与数据库无关了,因为是一个很通用的集合。
这里带来了一个问题,如果是远程处理,那么,用List这个返回数据还可以吗?
在服务器端,可能通过引用传递,变更是不用操心记录到后台的。有客户端如何处理呢?

#26楼 [楼主]   回复  引用  查看    

2008-02-21 11:22 by Jeffrey Zhao      
@光影传说
List自然可以序列化后进行传递,呵呵。

#27楼    回复  引用  查看    

2008-02-22 09:20 by 光影传说      
@Jeffrey Zhao
关键是如何记录变化了的数据,返回的List如何方便感知哪些数据是变化的,哪些数据是删除的,哪些数据是新添加的。因为从客户端传过来之后,则触发也断了,没有了Table<>,只有List如何处理呢?如果让手动去处理,那么,容易出错,工作量也是很大的。如果能把这个问题解决,我的ORM层会换成Linq。
从[Column(Storage="_CustomerID", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
public System.Guid CustomerID
{
get
{
return this._CustomerID;
}
set
{
if ((this._CustomerID != value))
{
this.OnCustomerIDChanging(value);
this.SendPropertyChanging();
this._CustomerID = value;
this.SendPropertyChanged("CustomerID");
this.OnCustomerIDChanged();
}
}
}

这里可以看的出,Linq的历史记录应该是保存在Table<>里的,我没有跟踪过,不敢确定。
当转换成List并序列化后,则这种关联应该去掉了。这种做法感觉是把单个对象与集合捆的太死,如果对象是单个的,则处理起来感觉有一些不方便,被束缚住了手脚。这也是比较困扰我的一个地方,我也想把这一块理清楚。
如果与我的服务层关联,也要对数据处理上加一些改造,还有业务层的组织,这些都要考虑,又是一个很大的工作量。
希望有机会能跟您探讨一下....

#28楼 [楼主]   回复  引用  查看    

2008-02-22 11:07 by Jeffrey Zhao      
@光影传说
不用担心,Translate后的数据是进入DataContext的Table中的,您可以修改并SubmitChanges,DataContext会跟踪对象状态。

#29楼    回复  引用  查看    

2008-02-22 11:30 by 光影传说      
@Jeffrey Zhao
关键是序列化、反序列化之后,如果全部是在服务器端运行,不跨进程、CPU、网络,应该没有问题,是通过引用传递的。但是序列化传输之后,已经没有了DataContext,没有了Table,如何跟踪对象状态?

#30楼 [楼主]   回复  引用  查看    

2008-02-22 14:53 by Jeffrey Zhao      
@光影传说
这也是非常常见的场景,是个ORM框架都会遇到,所以Table提供了Attach方法,用于把一个对象“附加”给某个DataContext.

#31楼    回复  引用  查看    

2008-02-22 18:02 by 光影传说      
@Jeffrey Zhao
处理的感觉是半手工处理,要很多人工代码参与进去。
示例上的代码与想像相似。是有Attach方法,但是达到的效果还有改进的余地。手工参与的还是比较多的。没有实现统一处理。

#32楼    回复  引用    

2008-03-06 08:21 by eidolon [未注册用户]
Attach并不是万能的, 更新效率不高, 更重要的还需要一个timestamp的字段来配合, 做版本比较, 这个比较郁闷.

#33楼 [楼主]   回复  引用  查看    

2008-03-06 11:03 by Jeffrey Zhao      
@eidolon
不过似乎没有更好的解决方案

#34楼    回复  引用    

2008-03-12 18:15 by Guest [未注册用户]
不太明白这句:因为默认情况下每个Item对象的ItemComment集合不会被同时查询出来,而是会等到内层的foreach循环执行时再次查询数据库


能详细一点吗?

#35楼    回复  引用    

2008-03-25 14:08 by Weatherreport [未注册用户]
public List<Item> GetItemsForListing(int ownerId)
{
ItemDataContext dataContext = new ItemDataContext();
var query = from item in dataContext.Items
where item.UserID == ownerId
orderby item.CreateTime descending
select new MyItem{
UserID = item.UserID
};

return query.ToList();
}

public MyItem : Item{}

这样的做法好像 还是可以的

#36楼    回复  引用  查看    

2008-03-26 09:36 by 光影传说      
@Weatherreport
这样处理是不错,但是根本的问题还是没有解决。

#37楼    回复  引用  查看    

2008-04-08 10:43 by S.Sams      
.ToList() 无法返回指定的实体类属性, 在最新版本中问题还没有解决.
方式1: var query = from s in siteconfig.SiteConfig select s;
方式2: var query = from s in siteconfig.SiteConfig
select new
{
s.ID,
s.Application,
s.CompanyName
};

在进行 .ToList() 转换时 方式2 还是出错, 需要转换的属性不存在!
最新的 .ExecuteQuery<T>("select id,application,companyname from").ToList() 是没问题, 但这样处理直接写SQl体现不出LinQ的优势.

有没有更好的解决方案?




#38楼 [楼主]   回复  引用  查看    

2008-04-08 14:51 by Jeffrey Zhao      
@S.Sams
抛出什么异常?生成的SQL语句是什么?

#39楼    回复  引用  查看    

2008-04-08 17:37 by Vincent Love      
linq to sql 里面有没有像link这样的关键字啊

StartsWith,contains好像不太符合要求

我想在一段字符串中查找某个词组,就是模糊查询
比如在【在LINQ to SQL中使用Translate方法以及修改查询用SQL】中找
LINQ to SQL

where title like '%LINQ to SQL%' 这种方式如何转换成LINQ to SQL的写法呢

#40楼 [楼主]   回复  引用  查看    

2008-04-08 23:23 by Jeffrey Zhao      
@Vincent Love
有些SQL语句的确是LINQ to SQL无法表示的。

#41楼    回复  引用  查看    

2008-04-09 12:07 by S.Sams      
@Jeffrey Zhao
编译无法通过 List<SiteConfig> 类型无法转换

@Vincent Love
like '%LINQ to SQL%' 这个是可以的
得用 SqlMethods.Like(a.Title, "C%")

#42楼 [楼主]   回复  引用  查看    

2008-04-09 12:09 by Jeffrey Zhao      
@S.Sams
生成的SQL语句是什么呢?

#43楼    回复  引用  查看    

2008-04-09 12:10 by S.Sams      
@Jeffrey Zhao
编译无法通过 List<SiteConfig> 类型无法转换
估计是在 from s in siteconfig.SiteConfig
select new
{
s.ID,
s.Application,
s.CompanyName
}; 时, 限制了属性的输出, 在用 ToList() 转换时 其它属性不存在.
而用 List<SiteConfig> = (from s in siteconfig.SiteConfig select s).ToList() 编译运行都是没问题的.

用ExecuteQuery是没问题的, 只是其它属性为空而已
.ExecuteQuery("select id,application,companyname from").ToList()

#44楼    回复  引用  查看    

2008-04-09 12:27 by Vincent Love      
--引用--------------------------------------------------
S.Sams: @Jeffrey Zhao
编译无法通过 List&lt;SiteConfig&gt; 类型无法转换

@Vincent Love
like '%LINQ to SQL%' 这个是可以的
得用 SqlMethods.Like(a.Title, &quot;C%&quot;)
--------------------------------------------------------
SqlMethods.Like 这个直接这么写吗?
在哪个命名空间下?

#45楼    回复  引用  查看    

2008-04-09 12:30 by Vincent Love      
@S.Sams
找到了,呵呵
using System.Data.Linq.SqlClient;

#46楼 [楼主]   回复  引用  查看    

2008-04-09 13:40 by Jeffrey Zhao