LINQ join failing between table and list of objects - c#

I need to perform an update on a table with values from a List of objects in C# .NET Core 3.0. I tried to use the Join method, but receive this error:
Processing of the LINQ expression
DbSet<Room>
.Join(
outer: __p_0,
inner: p => p.RoomId,
outerKeySelector: s => s.ruId,
innerKeySelector: (s, p) => new {
kuku = s,
riku = p
})
by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See link for more detailed information.
public class Room
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
public int RoomId { get; set; }
[StringLength(50, MinimumLength = 3)]
public string RoomAddress { get; set; }
}
public class roomsForUpdate
{
public int ruId { get; set; }
public string ruName { get; set; }
}
var roomList = new List<roomsForUpdate>() { new roomsForUpdate { ruId = 1, ruName = "aa" }, new roomsForUpdate { ruId = 2, ruName = "bb" } };
var result = _context.Room.Join(roomList, p => p.RoomId, s => s.ruId, (s, p) => new { kuku = s, riku = p }).ToList();

You cannot join the EF Core LINQ query with a local list, because it can't be translated into SQL. Better first you get the database data and then join in memory.

LINQ is not meant to change the sources, it can only extract data from the sources. If you need to update data, you first fetch the items that must be updated, then you update them. Alternatively you can use plain old SQL to update the data without fetching it first.
In local memory, you have a sequence of RoomsForUpdate. Every RoomForUpdate has an Id (RuId) and a Name.
In your database you have a table with Rooms, Every Room in this table has an Id in RoomId and a RoomAddress.
It seems to me, that you want to update all Rooms that have an RoomId, that is one of the RuIds in your sequence of RoomsForUpdate. In other words: fetch (some properties of) all Rooms that have a value for RoomId that is a RuId in your sequence of RoomsForUpdate:
var roomsToUpdate = new List<roomsForUpdate>()
{
new roomsForUpdate { ruId = 1, ruName = "aa" },
new roomsForUpdate { ruId = 2, ruName = "bb" }
};
// Extract the Ids of the rooms that must be fetched
var roomToUpdateIds = roomsToUpdate.Select(room => room.ruId);
// Fetch all rooms from the database that have a RoomId that is in this sequence
var fetchedRooms = dbContext.Rooms
.Where(room => roomToUpdateIds.Contains(room => room.RoomId)
.ToList();
Of course you can put everything into one big LINQ statement. This will not improve efficiency, however it will deteriorate readability of your code.
Now to update the Rooms, you'll have to enumerate them one by one, and give the fetched rooms new values. You didn't say which new value you want. I have an inkling that you want to assign RuName to RoomAddress. This means that you have to combine the Room with the new value for the RoomAddress.
This can be done by LINQ:
var roomsWithExpectedNewValues = fetchedRooms.Join(roomsToUpdate,
fetchedRoom => fetchedRoom.RoomId, // from every fetched room take the Id
roomToUpdate => roomToUpdate.RuId, // from every room to update take the RuId
// for every fetchedRoom with its matching room to update, make one new:
(fetchedRoom, roomToUpdate) => new
{
Room = fetchedRoom,
NewValue = roomToUpdate.RuName,
})
.ToList();
To actually perform the update, you'll have to enumerate this sequence:
foreach (var itemToUpdate in roomsWithExpectedNewValues)
{
// assign RuName to RoomName
itemToUpdate.Room.RoomName = itemToUpdate.NewValue;
}
dbContext.SaveChanges();
A little less LINQ
Although this works, there seems to be a lot of magic going on. The join will internally make a Dictionary for fast lookup, and throws it away. I think a little less LINQ will make it way more easy to understand what's going on.
// your original roomsToUpdate
var roomsToUpdate = new List<roomsForUpdate>()
{
new roomsForUpdate { ruId = 1, ruName = "aa" },
new roomsForUpdate { ruId = 2, ruName = "bb" }
};
var updateDictionary = roomsToUpdate.ToDictionary(
room => room.RuId, // key
room => room.RuName) // value
The Keys of the dictionary are the IDs of the rooms that you want to fetch:
// fetch the rooms that must be updated:
var fetchedRooms = dbContext.Rooms
.Where(room => updateDictionary.Keys.Contains(room => room.RoomId)
.ToList();
// Update:
foreach (var fetchedRoom in fetchedRooms)
{
// from the dictionary fetch the ruName:
var ruName = updateDicationary[fetchedRoom.RoomId];
// assign the ruName to RoomAddress
fetchedRoom.RoomAddress = ruName;
// or if you want, do this in one statement:
fetchedRoom.RoomAddress = updateDicationary[fetchedRoom.RoomId];
}
dbContext.SaveChanges();

Related

How to write a LINQ query that will return list of entities whose ID is included in specific list?

I have a list of Users.
User.cs
public Guid Id { get; set; }
public bool isActiveUser { get; set; }
I want to create a method that will receive list of Ids. And based on that, it will create/update/delete data from User table in DB.
If I have 3 User records in DB with ids {1, 2, 3}
And my method receives List userIds = {2, 3, 5}.
Outcome should be, in DB, User table should have 2, 3, 5. So, '1' will be removed, 2 and 3 will be updated(for simplicity not mentioning how) and '5' will be added.
I take these 3 types of ids like this :
var existingUserIds = existingsUsers.Select(x=>x.Id).ToList();
var toBeDeleted = existingsUsers.Except(userIdsFromParameters).ToList();
var toBeUpdated = existingUsers.Intersect(userIdsFromParameters).ToList();
var toBeAdded = userIdsFromParameters.Except(existingUsers).ToList();
//Delete operation
foreach (var id in toBeDeleted)
{
var obsoleteEntities = Users.Where(x => x.Id== id).ToList();
if (obsoleteEntities != null)
{
Users.RemoveRange(obsoleteEntities);
}
}
That seems like a lot of operations. Same goes with updates.
Is there a way to write a LINQ query, that will give me all the users whose IDs are included in toBeDeleted list?
var obsoleteEntities = Users.Where(x => toBeDeleted.Contains(x.Id)).ToList();
or
var obsoleteEntities = Users.Where(x => toBeDeleted.Any(tbd=>tbd==x.Id)).ToList();
Sounds like you need the Contains method:
var obsoleteEntities = Users.Where(x => toBeDeleted.Contains(x.Id)).ToList();

Random with condition

I have the following code to extract records from a dbcontext randomly using Guid class:
var CategoryList = {1,5};
var generatedQues = new List<Question>();
//Algorithm 1 :)
if (ColNum > 0)
{
generatedQues = db.Questions
.Where(q => CategoryList.Contains(q.CategoryId))
.OrderBy(q => Guid.NewGuid()).Take(ColNum).ToList();
}
First, I have a list of CategoryId stored in CategoryList as a condition to be fulfilled when getting records from the db. However, I would like to achieve an even distribution among the questions based on the CategoryId.
For example:
If the ColNum is 10, and the CategoryId obtained are {1,5}, I would like to achieve by getting 5 records that are from CategoryId = 1 and another set of 5 records from CategoryId = 5. If the ColNum is an odd number like 11, I would also like to achieve an even distribution as much as possible like maybe getting 5 records from CategoryId 1 and 6 records from CategoryId 2.
How do I do this?
This is a two step process,
Determine how many you want for each category
Select that many items from each category in a random order
For the first part, define a class to represent the category and how many items are required
public class CategoryLookup
{
public CategoryLookup(int catId)
{
this.CategoryId = catId;
}
public int CategoryId
{
get; private set;
}
public int RequiredAmount
{
get; private set;
}
public void Increment()
{
this.RequiredAmount++;
}
}
And then, given your inputs of the required categories and the total number of items required, work out how many are required for each category
var categoryList = new []{1,5};
var colNum = 7;
var categoryLookup = categoryList.Select(x => new CategoryLookup(x)).ToArray();
for(var i = 0;i<colNum;i++){
categoryLookup[i%categoryList.Length].Increment();
}
The second part is really easy, just use a SelectMany to get the list of questions (Ive used a straight linq to objects to test, should work fine for database query. questions in my code would just be db.Questions in yours)
var result = categoryLookup.SelectMany(
c => questions.Where(q => q.CategoryId == c.CategoryId)
.OrderBy(x => Guid.NewGuid())
.Take(c.RequiredAmount)
);
Live example: http://rextester.com/RHF33878
You could try something like this:
var CategoryList = {1,5};
var generatedQues = new List<Question>();
//Algorithm 1 :)
if (ColNum > 0 && CategoryList.Count > 0)
{
var take = // Calculate how many of each
// First category
var query = db.Questions
.Where(q => q.CategoryId == CategoryList[0])
.OrderBy(q => Guid.NewGuid()).Take(take);
// For all remaining categories
for(int i = 1; i < CategoryList.Count; i++)
{
// Calculate how many you want
take = // Calculate how many of each
// Union the questions for that category to query
query = query.Union(
query
.Where(q => q.CategoryId == CategoryList[i])
.OrderBy(q => Guid.NewGuid()).Take(take));
}
// Randomize again and execute query
generatedQues = query.OrderBy(q => Guid.NewGuid()).ToList()
}
The idea is to just get a random list for each category and add them all together. Then you randomize that again and create your list. I do not know if it will do all this on the database or in memory, but it should be database I think. The resulting SQL will look horrible though.

How to aggregate duplicate records and sum value using C#

I want to create a list where it holds several agents and the number of calls they make and did it like so:
public class Agent
{
public string Agent_ID{ get; set; }
public string Name { get; set; }
public int Calls { get; set; }
}
var list = new List<Agent>() // To create a list to hold the data
{
new Agent() { Agent_ID = "TK_J", Name = "James", Calls = 10 },
new Agent() { Agent_ID = "TK_K", Name = "Kurtis", Calls = 10 },
new Agent() { Agent_ID = "TK_R", Name = "Rebecca", Calls = 5 },
new Agent() { Agent_ID = "TK_J", Name = "James", Calls = 10 },
new Agent() { Agent_ID = "TK_R", Name = "Rebecca", Calls = 5 },
new Agents(){ Agent_ID = "TK_B", Name = "Bobby", Calls = 10 },
};
As you can see there will be redundant lines of data. So I want to use C# aggregation function such as group by to sum up the similar agent's number of calls. What I was trying was:
list.GroupBy(i => i.Agent_ID).Select(g => new
{
Agent_ID= g.Key,
Name = */ How do i bring the name here*/,
Calls = g.Sum(i => i.Calls)});
Anyone can help me? Appreciate any help or advice. If there is something wrong teach me how to fix the code. Many thanks!!
You are currently grouping by only AgentID.
You'll need to project the fields you require as an anonymous object so they can be available as an IGrouping<annonymous,Agent> to the select.
See below.
list.GroupBy(i => new {i.Agent_ID, i.Name}).Select(g => new
{
Agent_ID= g.Key.Agent_ID,
Name = g.Key.Name,
Calls = g.Sum(i => i.Calls)
});
Assuming that Agent_ID will be synchronized with the Agent's Name property, you can also use an aggregate like First() to return the the name from any Agent record in the group, since all agents in each group will have the same name:
list.GroupBy(i => i.Agent_ID)
.Select(g => new Agent // Strong class
{
Agent_ID= g.Key.Agent_ID,
Name = g.First().Name,
Calls = g.Sum(i => i.Calls)
})
Also, since you seem to be projecting back into the same shape, why not retain and project into the strong class, rather than a new anon class?

There is already an open DataReader associated

I am making this autocomplete search bar which contains title, description and category_id but I need the category name which is in another table so I take out the category id from my ads table and check the id with the table in category I know I need to close my connection to the database before I can make a new one so I need another way around it.
public class SetGetAds
{
public string Title { get; set; }
public string Description { get; set; }
public string Category { get; set; }
}
using (var db = new myProjectEnt())
{
var getAds = (from a in db.ads where a.title.Contains(searchQuery) select new { a.title, a.description, a.categories_id }).Take(15);
var ads = new List<SetGetAds>();
foreach (var setAds in getAds)
{
var getCategory = (from c in db.ads where c.title.Equals(setAds.categories_id) select new { c.title }).SingleOrDefault();
ads.Add(new SetGetAds { Title = setAds.title, Description = setAds.description, Category = getCategory.title });
var jsonString = new JavaScriptSerializer();
return jsonString.Serialize(ads);
}
}
getAds is an enumerable sequence that is lazily taking data from the reader - you then loop over that. Then, for each one you are performing a second query - getCategory. The important thing here is that getAds is still reading data - so yes, you have nested commands.
Options (in preference order, highest = preferred):
restructure the query to get the category at the same time, in one go - to avoid both the nested query and the obvious N+1 issue
add a .ToList() on the end of getAds, to complete the first query eagerly
enable MARS so that you are allowed to have nested queries
An N+1 issue is very commonly a source of performance problems; personally I would be looking to write this query in a way that avoids that, for example:
var ads = (from a in db.ads
where a.title.StartsWith(searchQuery)
join c in db.ads on a.categories_id equals c.title
select new { a.title, a.description, a.categories_id,
category = c.title }).Take(15);

Create GroupBy Statements Dynamically

I am trying to dynamically re-structure some data to be shown in a treeview which will allows the user to select up to three of the following dimensions to group the data by:
Organisation
Company
Site
Division
Department
So for example, if the user were to select that they wanted to group by Company then Site then Division...the following code would perform the required groupings.
var entities = orgEntities
// Grouping Level 1
.GroupBy(o => new { o.CompanyID, o.CompanyName })
.Select(grp1 => new TreeViewItem
{
CompanyID = grp1.Key.CompanyID,
DisplayName = grp1.Key.CompanyName,
ItemTypeEnum = TreeViewItemType.Company,
SubItems = grp1
// Grouping Level 2
.GroupBy(o => new { o.SiteID, o.SiteName })
.Select(grp2 => new TreeViewItem
{
SiteID = grp2.Key.SiteID,
DisplayName = grp2.Key.SiteName,
ItemTypeEnum = TreeViewItemType.Site,
SubItems = grp2
// Grouping Level 3
.GroupBy(o => new { o.Division })
.Select(grp3 => new TreeViewItem
{
DisplayName = grp3.Key.Division,
ItemTypeEnum = TreeViewItemType.Division,
}).ToList()
}).ToList()
})
.ToList();
This would give a structre like this:
+ Company A
+ Site A
+ Division 1
+ Division 2
+ Site B
+ Division 1
+ Company B
+ Site C
+ Division 2
+ Company C
+ Site D
However, this only provides me with on of a large number of combinations.
How would I go about converting this into something that could create the equivalent expression dynamically based on the three dimensions that the user has chosen and so I don't have to create one of each of these expressions for each combination!!?
Thanks guys.
An intriguing problem. Choosing a single type for grouping keys and another type for results... makes it is very possible to get what you're asking for.
public struct EntityGroupKey
{
public int ID {get;set;}
public string Name {get;set;}
}
public class EntityGrouper
{
public Func<Entity, EntityGroupKey> KeySelector {get;set;}
public Func<EntityGroupKey, TreeViewItem> ResultSelector {get;set;}
public EntityGrouper NextGrouping {get;set;} //null indicates leaf level
public List<TreeViewItem> GetItems(IEnumerable<Entity> source)
{
var query =
from x in source
group x by KeySelector(x) into g
let subItems = NextGrouping == null ?
new List<TreeViewItem>() :
NextGrouping.GetItems(g)
select new { Item = ResultSelector(g.Key), SubItems = subItems };
List<TreeViewItem> result = new List<TreeViewItem>();
foreach(var queryResult in query)
{
// wire up the subitems
queryResult.Item.SubItems = queryResult.SubItems
result.Add(queryResult.Item);
}
return result;
}
}
Used in this way:
EntityGrouper companyGrouper = new EntityGrouper()
{
KeySelector = o => new EntityGroupKey() {ID = o.CompanyID, Name = o.CompanyName},
ResultSelector = key => new TreeViewItem
{
CompanyID = key.ID,
DisplayName = key.Name,
ItemTypeEnum = TreeViewItemType.Company
}
}
EntityGrouper divisionGrouper = new EntityGrouper()
{
KeySelector = o => new EntityGroupKey() {ID = 0, Name = o.Division},
ResultSelector = key => new TreeViewItem
{
DisplayName = key.Name,
ItemTypeEnum = TreeViewItemType.Division
}
}
companyGrouper.NextGrouping = divisionGrouper;
List<TreeViewItem> oneWay = companyGrouper.GetItems(source);
companyGrouper.NextGrouping = null;
divisionGrouper.NextGrouping = companyGrouper;
List<TreeViewItem> otherWay = divisionGrouper.GetItems(source);
Another option is to use DynamicLinq. If this is straight LINQ (not through some DB context such as LINQ2SQL), then this can be done by composing your grouping/selector strings:
var entities = orgEntities
.GroupBy("new(CompanyID, CompanyName)", "it", null) // DynamicLinq uses 'it' to reference the instance variable in lambdas.
.Select(grp1 => new TreeViewItem
{
...
.GroupBy("new(SiteID, o.SiteName)", "it", null)
// And so on...
You can probably abstract this into each of the criteria type. The only issue I see is the inner groupings might not be the easiest to compile together, but at least this can get you started in some direction. DynamicLinq allows you to build dynamic types, so it's certainly possible to abstract it even further. Ultimately, the biggest challenge is that based on what you're grouping by, the generated TreeViewItem contains different information. Good use case for dynamic LINQ, but the only problem I see is abstracting even further down the line (to the inner groupings).
Let us know what you come up with, definitely an interesting idea that I hadn't considered before.

Categories

Resources