实体框架与 LINQ 聚合来连接字符串?

发布于 2024-10-03 07:34:20 字数 954 浏览 1 评论 0原文

这对我来说在 TSQL 中很容易执行,但我只是坐在这里用头撞桌子,试图让它在 EF4 中工作!

我有一个表,我们称之为 TestData。它有字段,例如:DataTypeID、Name、DataValue。

DataTypeID, Name, DataValue
1,"Data 1","Value1"
1,"Data 1","Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

我想对 DataID/Name 进行分组,并将 DataValue 连接到 CSV 字符串中。所需的结果应该包含 -

DataTypeID, Name, DataValues
1,"Data 1","Value1,Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

现在,这就是我尝试执行的方法 -

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = (string)g.Aggregate("", (a, b) => (a != "" ? "," : "") + b.DataValue),
 }).ToList()

问题是 LINQ to Entities 不知道如何将其转换为 SQL。这是 3 个 LINQ 查询联合的一部分,我真的希望它保持这种状态。我想我可以检索数据,然后稍后执行聚合。出于性能原因,这不适用于我的应用程序。我还考虑过使用 SQL Server 功能。但这在 EF4 世界中似乎并不“正确”。

有人愿意尝试一下吗?

This is easy for me to perform in TSQL, but I'm just sitting here banging my head against the desk trying to get it to work in EF4!

I have a table, lets call it TestData. It has fields, say: DataTypeID, Name, DataValue.

DataTypeID, Name, DataValue
1,"Data 1","Value1"
1,"Data 1","Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

I want to group on DataID/Name, and concatenate DataValue into a CSV string. The desired result should contain -

DataTypeID, Name, DataValues
1,"Data 1","Value1,Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

Now, here's how I'm trying to do it -

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = (string)g.Aggregate("", (a, b) => (a != "" ? "," : "") + b.DataValue),
 }).ToList()

The problem is that LINQ to Entities does not know how to convert this into SQL. This is part of a union of 3 LINQ queries, and I'd really like it to keep it that way. I imagine that I could retrieve the data and then perform the aggregate later. For performance reasons, that wouldn't work for my app. I also considered using a SQL server function. But that just doesn't seem "right" in the EF4 world.

Anyone care to take a crack at this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

独闯女儿国 2024-10-10 07:34:20

如果 ToList() 是原始查询的一部分,而不仅仅是为此示例添加的,则在结果列表上使用 LINQ to Objects 进行聚合:

var query = (from t in context.TestData
            group t by new { DataTypeID = t.DataTypeID, Name = t.Name } into g 
            select new { DataTypeID = g.Key.DataTypeID, Name = g.Key.Name, Data = g.AsEnumerable()})
            .ToList()
            .Select (q => new { DataTypeID = q.DataTypeID, Name = q.Name, DataValues = q.Data.Aggregate ("", (acc, t) => (acc == "" ? "" : acc + ",") + t.DataValue) });

在 LINQPad 中测试,它会生成以下结果:

< img src="https://i.sstatic.net/zhj90.jpg" alt="替代文字">

If the ToList() is part of your original query and not just added for this example, then use LINQ to Objects on the resulting list to do the aggregation:

var query = (from t in context.TestData
            group t by new { DataTypeID = t.DataTypeID, Name = t.Name } into g 
            select new { DataTypeID = g.Key.DataTypeID, Name = g.Key.Name, Data = g.AsEnumerable()})
            .ToList()
            .Select (q => new { DataTypeID = q.DataTypeID, Name = q.Name, DataValues = q.Data.Aggregate ("", (acc, t) => (acc == "" ? "" : acc + ",") + t.DataValue) });

Tested in LINQPad and it produces this result:

alt text

老娘不死你永远是小三 2024-10-10 07:34:20

一些答案建议调用 ToList(),然后作为 LINQ to OBJECT 执行计算。对于少量数据来说这很好,但是如果我有大量数据并且不想过早加载到内存中,那么 ToList() 可能不是一个选择。

因此,更好的想法是在表示层中处理/格式化数据,并让数据访问层仅加载或保存 SQL 喜欢的原始数据。
此外,在表示层中,很可能您正在通过分页过滤数据,或者您可能在详细信息页面中显示一行,因此,您将加载到内存中的数据可能小于您从数据库加载的数据。 (你的情况/架构可能会有所不同,..但我是说,最有可能)。

我有类似的要求。我的问题是从实体框架对象中获取项目列表并创建一个格式化字符串(逗号分隔值)

  1. 我在视图模型中创建了一个属性,它将保存来自存储库的原始数据,并在填充该属性时, LINQ 查询不会成为问题,因为您只是查询 SQL 可以理解的内容。

  2. 然后,我在 ViewModel 中创建了一个仅获取属性,该属性读取原始实体属性并在显示之前格式化数据。

     公共类 MyViewModel
     {
         公共 IEnumerable; RawChildItems { 获取;放; }
    
         公共字符串格式化数据
         {
             得到
             {
                 if (this.RawChildItems == null)
                     返回字符串。空;
    
                 string[] theItems = this.RawChildItems.ToArray();
    
                 返回 theItems.Length > 0
                     ? string.Format("{0} ( {1} )", this.AnotherRegularProperty, String.Join(", ", theItems.Select(z => z.Substring(0, 1))))
                     : 字符串.空;
             }
         }
     }
    

好的,这样,我就可以轻松地将数据从 LINQ to Entity 加载到此视图模型中,而无需调用.ToList()。

示例:

IQueryable<MyEntity> myEntities = _myRepository.GetData();

IQueryable<MyViewModel> viewModels = myEntities.Select(x => new MyViewModel() { RawChildItems = x.MyChildren })

现在,我可以在需要时随时调用 MyViewModel 的 FormattedData 属性,并且只有在调用该属性时才会执行 Getter,这是此模式的另一个好处(延迟处理)。

架构建议:我强烈建议让数据访问层远离所有格式或视图逻辑或 SQL 无法理解的任何内容。

您的实体框架类应该是简单的 POCO,可以直接映射到数据库列,而无需任何特殊的映射器。您的数据访问层(例如使用 LINQ to SQL 从 DbContext 获取数据的存储库)应该仅获取直接存储在数据库中的数据。没有多余的逻辑。

然后,您应该为表示层(例如 ViewModels)提供一组专用的类,其中包含用于格式化用户喜欢查看的数据的所有逻辑。这样,您就不必与实体框架 LINQ 的限制作斗争。我永远不会将实体框架模型直接传递给视图。我也不会让我的数据访问层为我创建 ViewModel。创建 ViewModel 可以委托给您的领域服务层或应用程序层,这是比您的数据访问层更上层。

Some of the Answers suggest calling ToList() and then perform the calculation as LINQ to OBJECT. That's fine for a little amount of data, but what if I have a huge amount of data that I do not want to load into memory too early, then, ToList() may not be an option.

So, the better idea would be to process/format the data in the presentation layer and let the Data Access layer do only loading or saving raw data that SQL likes.
Moreover, in your presentation layer, most probably you are filtering the data by paging, or maybe you are showing one row in the details page, so, the data you will load into the memory is likely smaller than the data you load from the database. (Your situation/architecture may be different,.. but I am saying, most likely).

I had a similar requirement. My problem was to get the list of items from the Entity Framework object and create a formatted string (comma separated value)

  1. I created a property in my View Model which will hold the raw data from the repository and when populating that property, the LINQ query won't be a problem because you are simply querying what SQL understands.

  2. Then, I created a get-only property in my ViewModel which reads that Raw entity property and formats the data before displaying.

     public class MyViewModel
     {
         public IEnumerable<Entity> RawChildItems { get; set; }
    
         public string FormattedData
         {
             get
             {
                 if (this.RawChildItems == null)
                     return string.Empty;
    
                 string[] theItems = this.RawChildItems.ToArray();
    
                 return theItems.Length > 0
                     ? string.Format("{0} ( {1} )", this.AnotherRegularProperty, String.Join(", ", theItems.Select(z => z.Substring(0, 1))))
                     : string.Empty;
             }
         }
     }
    

Ok, in that way, I loaded the Data from LINQ to Entity to this View Model easily without calling.ToList().

Example:

IQueryable<MyEntity> myEntities = _myRepository.GetData();

IQueryable<MyViewModel> viewModels = myEntities.Select(x => new MyViewModel() { RawChildItems = x.MyChildren })

Now, I can call the FormattedData property of MyViewModel anytime when I need and the Getter will be executed only when the property is called, which is another benefit of this pattern (lazy processing).

An architecture recommendation: I strongly recommend to keep the data access layer away from all formatting or view logic or anything that SQL does not understand.

Your Entity Framework classes should be simple POCO that can directly map to a database column without any special mapper. And your Data Access layer (say a Repository that fetches data from your DbContext using LINQ to SQL) should get only the data that is directly stored in your database. No extra logic.

Then, you should have a dedicated set of classes for your Presentation Layer (say ViewModels) which will contain all logic for formatting data that your user likes to see. In that way, you won't have to struggle with the limitation of Entity Framework LINQ. I will never pass my Entity Framework model directly to the View. Nor, I will let my Data Access layer creates the ViewModel for me. Creating ViewModel can be delegated to your domain service layer or application layer, which is an upper layer than your Data Access Layer.

愛放△進行李 2024-10-10 07:34:20

感谢 moi_meme 的回答。我希望用 LINQ to Entities 做的事情是不可能的。正如其他人所建议的,您必须使用 LINQ to Objects 来访问字符串操作方法。

有关更多信息,请参阅 moi_meme 发布的链接。

更新 8/27/2018 - 更新链接(再次) - https://web.archive.org/web/20141106094131/http://www.mythos-rini.com/blog/archives/4510

并且因为我对 8 年前的仅链接答案感到不满,所以我会澄清一下,以防有一天存档副本消失。其基本要点是您无法在 EF 查询中访问 string.join。您必须创建 LINQ 查询,然后调用 ToList() 才能针对数据库执行查询。然后,数据就存储在内存中(又名 LINQ to Objects),这样您就可以访问 string.join。

上面引用的链接中的建议代码如下 -

var result1 = (from a in users
                b in roles
           where (a.RoleCollection.Any(x => x.RoleId = b.RoleId))
           select new 
           {
              UserName = a.UserName,
              RoleNames = b.RoleName)                 
           });

var result2 = (from a in result1.ToList()
           group a by a.UserName into userGroup
           select new 
           {
             UserName = userGroup.FirstOrDefault().UserName,
             RoleNames = String.Join(", ", (userGroup.Select(x => x.RoleNames)).ToArray())
           });

作者进一步建议将 string.join 替换为aggregate以获得更好的性能,如下所示 -

RoleNames = (userGroup.Select(x => x.RoleNames)).Aggregate((a,b) => (a + ", " + b))

Thanks to moi_meme for the answer. What I was hoping to do is NOT POSSIBLE with LINQ to Entities. As others have suggested, you have to use LINQ to Objects to get access to string manipulation methods.

See the link posted by moi_meme for more info.

Update 8/27/2018 - Updated Link (again) - https://web.archive.org/web/20141106094131/http://www.mythos-rini.com/blog/archives/4510

And since I'm taking flack for a link-only answer from 8 years ago, I'll clarify just in case the archived copy disappears some day. The basic gist of it is that you cannot access string.join in EF queries. You must create the LINQ query, then call ToList() in order to execute the query against the db. Then you have the data in memory (aka LINQ to Objects), so you can access string.join.

The suggested code from the referenced link above is as follows -

var result1 = (from a in users
                b in roles
           where (a.RoleCollection.Any(x => x.RoleId = b.RoleId))
           select new 
           {
              UserName = a.UserName,
              RoleNames = b.RoleName)                 
           });

var result2 = (from a in result1.ToList()
           group a by a.UserName into userGroup
           select new 
           {
             UserName = userGroup.FirstOrDefault().UserName,
             RoleNames = String.Join(", ", (userGroup.Select(x => x.RoleNames)).ToArray())
           });

The author further suggests replacing string.join with aggregate for better performance, like so -

RoleNames = (userGroup.Select(x => x.RoleNames)).Aggregate((a,b) => (a + ", " + b))
娇纵 2024-10-10 07:34:20

你们已经非常接近了。试试这个:

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = String.Join(",", g),
 }).ToList()

或者,如果 EF 不允许 String.Join(Linq-to-SQL 允许),您也可以这样做:

var qs = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = g
 }).ToArray();

var query = (from q in qs
            select new
            {
                q.DataTypeID,
                q.Name,
                DataValues = String.Join(",", q.DataValues),
            }).ToList();

You are so very close already. Try this:

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = String.Join(",", g),
 }).ToList()

Alternatively, you could do this, if EF doesn't allow the String.Join (which Linq-to-SQL does):

var qs = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = g
 }).ToArray();

var query = (from q in qs
            select new
            {
                q.DataTypeID,
                q.Name,
                DataValues = String.Join(",", q.DataValues),
            }).ToList();
祁梦 2024-10-10 07:34:20

也许在数据库上为此创建一个视图(它为您连接字段)然后让 EF 使用此视图而不是原始表是一个好主意?

我确信这在 LINQ 语句或映射详细信息中是不可能的。

Maybe it's a good idea to create a view for this on the database (which concatenates the fields for you) and then make EF use this view instead of the original table?

I'm quite sure it's not possible in a LINQ statement or in the Mapping Details.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文