简体   繁体   English

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

[英]Entity Framework with LINQ aggregate to concatenate string?

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

I have a table, lets call it TestData.我有一张表,我们称它为 TestData。 It has fields, say: DataTypeID, Name, DataValue.它有字段,比如说: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.我想对 DataID/Name 进行分组,并将 DataValue 连接成一个 CSV 字符串。 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.问题是 LINQ to Entities 不知道如何将其转换为 SQL。 This is part of a union of 3 LINQ queries, and I'd really like it to keep it that way.这是 3 个 LINQ 查询联合的一部分,我真的很希望它保持这种状态。 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.我还考虑过使用 SQL 服务器函数。 But that just doesn't seem "right" in the EF4 world.但这在 EF4 世界中似乎并不“正确”。

Anyone care to take a crack at this?有人愿意尝试一下吗?

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:如果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) });

Tested in LINQPad and it produces this result:在 LINQPad 中测试并产生以下结果:

替代文字

Some of the Answers suggest calling ToList() and then perform the calculation as LINQ to OBJECT.一些答案建议调用 ToList(),然后将计算作为 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.这对于少量数据来说很好,但是如果我有大量数据不想过早加载到内存中怎么办,那么 ToList() 可能不是一个选项。

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.因此,更好的想法是在表示层处理/格式化数据,让数据访问层只加载或保存 SQL 喜欢的原始数据。 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.我在我的视图模型中创建了一个属性,它将保存存储库中的原始数据,并且在填充该属性时,LINQ 查询不会成为问题,因为您只是查询 SQL 理解的内容。

  2. Then, I created a get-only property in my ViewModel which reads that Raw entity property and formats the data before displaying.然后,我在我的 ViewModel 中创建了一个只读属性,它读取原始实体属性并在显示之前格式化数据。

     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().好的,通过这种方式,我无需调用 ToList() 即可轻松地将数据从 LINQ to Entity 加载到此视图模型。

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).现在,我可以在需要时随时调用 MyViewModel 的 FormattedData 属性,并且只有在调用该属性时才会执行 Getter,这是此模式(惰性处理)的另一个好处。

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.架构建议:我强烈建议让数据访问层远离所有格式或视图逻辑或 SQL 不理解的任何内容。

Your Entity Framework classes should be simple POCO that can directly map to a database column without any special mapper.您的实体框架类应该是简单的 POCO,无需任何特殊映射器即可直接映射到数据库列。 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.您的数据访问层(比如使用 LINQ to SQL 从 DbContext 获取数据的存储库)应该只获取直接存储在数据库中的数据。 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.然后,您应该为您的表示层(例如 ViewModel)设置一组专用的类,其中将包含用于格式化用户喜欢看到的数据的所有逻辑。 In that way, you won't have to struggle with the limitation of Entity Framework LINQ.这样,您就不必为 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.也不,我会让我的数据访问层为我创建 ViewModel。 Creating ViewModel can be delegated to your domain service layer or application layer, which is an upper layer than your Data Access Layer.创建 ViewModel 可以委托给您的域服务层或应用层,它比您的数据访问层上层。

Thanks to moi_meme for the answer.感谢 moi_meme 的回答。 What I was hoping to do is NOT POSSIBLE with LINQ to Entities.我希望使用 LINQ to Entities 做的事情是不可能的。 As others have suggested, you have to use LINQ to Objects to get access to string manipulation methods.正如其他人所建议的那样,您必须使用 LINQ to Objects 才能访问字符串操作方法。

See the link posted by moi_meme for more info.有关详细信息,请参阅 moi_meme 发布的链接。

Update 8/27/2018 - Updated Link (again) - https://web.archive.org/web/20141106094131/http://www.mythos-rini.com/blog/archives/4510更新 8/27/2018 - 更新链接(再次) - 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.而且由于我对 8 年前的仅链接答案持怀疑态度,所以我会澄清以防存档副本有一天消失。 The basic gist of it is that you cannot access string.join in EF queries.它的基本要点是您不能在 EF 查询中访问 string.join。 You must create the LINQ query, then call ToList() in order to execute the query against the db.您必须创建 LINQ 查询,然后调用 ToList() 才能对数据库执行查询。 Then you have the data in memory (aka LINQ to Objects), so you can access string.join.然后您将数据保存在内存中(又名 LINQ to Objects),因此您可以访问 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 -作者进一步建议用 aggregate 替换 string.join 以获得更好的性能,就像这样 -

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

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):或者,如果 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();

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

I'm quite sure it's not possible in a LINQ statement or in the Mapping Details.我很确定这在 LINQ 语句或映射详细信息中是不可能的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM