2014-07-15 100 views
0

我有两个实体类UserConnectionUser具有Connection的集合。DbContext SqlQuery返回意外结果

class User 
{ 
    public string username {get; set;} 
    public ICollection<Connection> Connections {get; set;} 
} 

class Connection 
{ 
    public string ConnectionId {get; set;} 
    public string RoomName {get; set;} 
} 

我有了这个SQL查询来检索连接列表供用户

// this code can be moved to the database as a stored procedure or view 
var sql = @"SELECT Users.UserName, Users.UserPix, 
           Connections.ConnectionId, Connections.RoomName, Connections.DateCreated 
      FROM   Users CROSS JOIN Connections 
      WHERE  (Users.UserName = @p0) AND (Connections.RoomName = @p1)"; 

return _context.Users.SqlQuery(sql, username, roomName).FirstOrDefault(); 

它返回一个用户对象有一个空连接列表,而不是填充连接与返回的数据数据库。 我试过用内连接替换交叉连接,但仍然是相同的结果

我不知道如何修改sql查询以便返回实际数据。我该怎么做,或者有什么我失踪?

+2

,我不认为你可以返回与SqlQuery类子对象,除非你使用延迟加载。 – DavidG

+0

我认为sql查询可以通过一种方式进行修改,使得完美的转换可能像是子查询或其他东西。 – pmbanugo

+2

我不这么认为。 – DavidG

回答

0

因为你没有做什么特别的东西在这里(甚至没有任何预测),您可以直接从上下文中返回实体,包括您想要的导航属性:

eg考虑到这些实体:

class User 
{ 
    public string username { get; set; } 
    public ICollection<Connection> Connections { get; set; } 
} 

class Connection 
{ 
    public string ConnectionId { get; set; } 
    public string RoomName { get; set; } 
} 

一到一对多的关系应用户之间存在于数据库中 - >连接

为了获得特定用户的所有连接所有用户的所有连接,或者,或者你能想到的与同时保留您只需在System.Data.Entity

签名使用.Include()QueryableExtensions关系的用户/连接过滤器/集料等查询的任意组合是这样,如果你有兴趣:

public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class

大约是EF一件好事,你可以渴望按需加载的子实体,而不必惹实体设计或添加不包含导航性能的新实体类

所以基本上它归结为:

using(YourDbContext context = new YourDbContext()) 
{ 
    var query = context.Users.Include(user => user.Connections); 

    // Do stuff with query 
} 

不要忘了延迟执行 - 如果你不知道延迟执行是什么,查询是如何建立起来的EF这是值得它 - 在一般的经验法则是;查询数据库的次数越少,在C#中执行的处理越少(对于聚合数据库类型的操作)应用程序执行的速度越快 - 因此,确保在完全构建查询之前不要枚举结果或者你将要使用LINQ to对象在.NET端进行所有的数字处理,而不是在SQL中它应该是的!

所以调用.ToList()或枚举上面的查询会得到你的结果

EF会翻译任何你已经使用你所选择的供应商(可能System.Data.SqlClient)做了正确的SQL方言。你不需要写任何SQL ...

查询的其他一些例子,你可以这样做:

// Get me users called Fred including their connection details 
context.Users.Include(x => x.Connections).Where(u => u.Username == "Fred") 

// Get me users that are currently connected to "Game Room" 
context.Users.Include(x => x.Connections).Where(u => u.Connections.Any(c => c.RoomName == "Game Room") 

无的,这需要你写任何SQL - 把运行这些查询时看到EF做什么在SQL跟踪,通常会写查询比你曾经可能:)(仅有时没有,它通常是当你正在做一些愚蠢的)

编辑

好,我看你是想筛选的用户和通过导线回来的连接,在这种情况下,您需要将导航属性明确加载为单独的查询,或使用投影进行过滤。

显式加载

var user = context.Users.First(x => x.UserName == username); 

context.Entry(user).Collection(x => x.Connections).Query().Where(x => x.RoomName == roomName).Load(); 

这也导致两个查询

投影

var usersConnections = context.Users 
.Where(u => u.UserName == userName) 
.Select(u => new 
{ 
    User = u, 
    Connections = u.Connections.Where(c => c.RoomName == roomName) 
}); 

这导致含有User属性一个匿名类型和Connections财产。您可以随时投射到一个已知类型太多,如果你需要通过某种域边界的

这将是对数据源的单一查询发送此

+0

我正在使用显式加载方法,因为它返回用户和连接,投影样式返回您必须投影/变换到的用户和连接列表您的预期类型。 – pmbanugo

+0

如果你想要创建一个匿名类型,你可以在投影期间新增类型,除非你满意多个查询的小性能损失(并且它可能会很小) – Charleh

+0

我在LinqPad上测试了这两个查询,带投影的那个返回了'IEnumerable '和'IEnumerable '。 'User'集合具有填充正确值的连接列表,'Connection'列表具有相同的连接。如果我投影到一个命名类型,它将必须是一个类型的属性为'IEnumerable '和'IEnumerable ',然后挑出第一个元素'type.Users.FirstOrDefault()'返回一个用户并且它是关联的连接。我猜显式加载会有非常小的性能影响,或者我可能会使用投影? – pmbanugo

0

要做到这一点,我首先检索/检查用户是否存在,那么我找回它们的连接,并将其添加到连接列表中的用户

public User GetUserAndConnections(string username, string roomname) 
{ 
    var user = _context.Users.Find(username); 
    if (user != null) 
    { 
     var connections = 
      _context.Users.Where(u => u.UserName == username) 
       .SelectMany(x => x.Connections.Where(p => p.RoomName == roomName)) 
       .ToList(); 
     user.AddExistingConnections(connections); 
    } 

    return user; 
} 
+0

为什么不直接使用实体框架和导航属性呢?你为什么要加入?交叉连接从右表中为左表中的每一行返回一行。你想要一个左连接 - 但实际上你不想要一个你想要实体框架为你实现的连接 – Charleh

+0

在编写针对数据源的简单的非特定于提供者的特定SQL时,你基本上错过了ORM的全部要点。你可以用'context.Users.Include(x => x.Connections)' – Charleh

+1

@Charleh得到相同的结果你可以举个例子。你甚至可以编辑答案 – pmbanugo