Dictionary problems: results.Read<NotificationRule>().ToDictionary(rule => rule.Id) states duplicate key - c#

I have a slight problem I don't seem to understand.
I have this bit of code:
SELECT sr.RuleId, s.Id, s.Name FROM Sites s
INNER JOIN NotificationSiteRules sr ON s.Id = sr.SiteID
WHERE sr.RuleId IN (
SELECT r.Id FROM NotificationRules r
INNER JOIN NotificationSiteRules sr ON r.Id = sr.RuleId
WHERE r.IsDeleted = 0 AND (#siteId IS NULL OR sr.SiteId = #siteId)
)
which returns the following set:
1 1 SiteOne
3 1 SiteOne
7 1 SiteOne
1 5 SiteTwo
As you can see, for rule 1 I have both SiteOne and SiteTwo. This must be permitted.
The definition of NotificationRule object is:
public class NotificationRule
{
public NotificationRule()
{
Sites = new List<Site>();
Recipients = new List<Recipient>();
}
public int? Id { get; set; }
public string Name { get; set; }
public List<Site> Sites { get; set; }
public List<Recipient> Recipients { get; set; }
}
So in this definition, it's actually stated that by each Id I should be able to have a list of Sites... But I am getting
System.ArgumentException: An item with the same key has already been added.
when I do
var rules = results.Read<NotificationRule>().ToDictionary(rule => rule.Id);
What am I doing wrong?
Sorry, I am editing the question because I am afraid I was not clear as to what I am trying to achieve.
The final result I was hoping for is of this form:
{1, [1,5],[SiteOne, SiteTwo]}
Which would correspond to:
{Key, List<Recipient>, list<Site>}
As you can see, in this construct I wouldn't have two keys, because all ends up into the same element.

As long as objects in the collection results have not unique values in the field Id you have to group results before put them to the dictionary.
results.Read<NotificationRule>()
.GroupBy(rule=>rule.Id).ToDictionary(group => group.Key, group=>group.ToArray());

It sounds like you may be wanting to do a group by rather than creating a dictionary.
var rules = results.Read<NotificationRule>().GroupBy(k => rule.Id);
This will group the rules by rule.Id
or if you are just trying to get the sites for a specific rule you could do
var siteId = 1;
var sites = results.Read<NotificationRule>().Where(r => r.Id == siteId);

Actually I just found that the problem is not in that little bit of code; that one is handled ok. The problem is that #siteId is arriving null, and then I get duplicates in a previous query.
I am closing this question as the point is now moot; I need to figure out a way to fix the previous query to get me the correct value... or a way to handle that null.
Thank you all for your help!

Related

LINQ - Simulating multiple columns in IN clausule

In oracle I can do the following query:
SELECT *
FROM Tabl Tabb
WHERE (tabb.Col1, tabb.Col2) IN ( (1,2), (3,4))
Consider I 've following entity:
public class Tabb
{
public int Col1 {get; set; }
public int Col2 {get; set; }
// other props
}
and criteria class
public class Search
{
public int Col1 {get; set; }
public int Col2 {get; set; }
}
I need to write:
public IEnumerable<Tabb> Select(IEnumerable<Search> s)
{
var queryable = this.context.Tabbs;
return queryable.Where(\* some *\).ToList();
}
How can I select entities, that search collection contain instance of search that has the same value of Col1 and Col2?
EDIT:
var result = from x in entity
join y in entity2
on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
It doesn't work (As I expected) - in may case entity2 is not a entity table, it is static collection, so EF throws exception (sth like: cannot find mapping layer to type Search[]);
There's a few ways, which all have pros and cons, and are sometimes a little bit tricky...
Solution 1
You enumerate the ef part first (of course, depending on the size of your data, this might be a very bad idea)
Solution 2
You concatenate your fields with an element you're sure (hum) you won't find in your fields, and use a Contains on concatenated EF data.
var joinedCollection =entity2.Select(m => m.field1 + "~" + m.field2);
var result = entity.Where(m => joinedCollection.Contains(m.field1 + "~" + m.field2));
of course, this would be a little bit more complicated if field1 and field2 are not string, you'll have to use something like that
SqlFunctions.StringConvert((double)m.field1) + "~" + //etc.
Solution 3
you do this in two step, assuming you will have "not too much result" with a partial match (on only one field)
var field1Collection = joinedCollection.Select(m => m.field1);
var result = entity.Where(m => joinedCollection.Contains(m.field1)).ToList();
then you make the "complete join" on the two enumerated lists...
Solution 4
use a stored procedure / generated raw sql...
Just understood the problem better. You want all rows where the columns match, may be this will help:
myDBTable.Where(x =>
myStaticCollection.Any(y => y.Col2 == x.Col2) &&
myStaticCollection.Any(y => y.Col1 == x.Col1))
.ToList()
.Select(x => new Search { Col1 = x.Col1, Col2 = x.Col2 });
This is saying, I want each row where any Col2 in my static collection matches this database Col2 AND where any Col1 matches this database Col1
this.context.Searches.Join(
this.context.Tabbs,
s => s.Col2,
t => t.Col2,
(search, tab) => new {
search,
tab
});
This will bring back IEnumerable<'a> containing a search and a tab
This guy is doing something similar LINK
var result = from x in entity
join y in entity2
on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
Once you have your result then you want to enumerate that to make sure you're hitting the database and getting all your values back. Once they're in memory, then you can project them into objects.
result.ToList().Select(a => new MyEntity { MyProperty = a.Property });

Refine LINQ join of two List<T> Objects, Group, and Count

I have two List objects. I want a count of how many items between the two list match. Now I could loop through with a counter as I come across matches....but that would be kinda lame.
However this one is stretching my LINQ knowledge. I believe what I want to do is Join, discern (where), group, and project the count. I came to this approach by reading similar SO questions and LINQ documentation.
However if this is flawed by all means it doesn't have to be this way. I just want the count of matching elements.
So my "master" object is:
public class Master
{
public StringModel OldText { get; private set; }
public StringModel NewText { get; private set; }
public Master()
{
OldText = new StringModel();
NewText = new StringModel();
}
}
StringModel is:
public class StringModel
{
public List<strPiece> Lines { get; private set; }
public StringModel()
{
Lines = new List<strPiece>();
}
}
My LINQ thus far is:
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
where oldLine.Type == "ABC"
group oldLine by --as you can see I kinda break down here.
Any help finishing would be appreciated. Also if I need to post more code just let me know.
Thank You
Sounds like a good use for intersect
var infoQuery =
(from cust in db.Customers
select cust.Country)
.Intersect
(from emp in db.Employees
select emp.Country)
;
Just perform a GroupJoin rather than a Join.
var unchangedJoin = from oldLine in Master.OldText.Lines
join newLine in Master.NewText.Lines
on oldLine.Type equals newLine.Type
into newMatches
where oldLine.Type == "ABC"
select oldLine;
var matches = unchangedJoin.Count();
I too would recommend Intersect. If you go that way, using the fluent syntax makes more sense.
int matches = Master.OldText.Lines
.Where(line => line.Type == "ABC")
.Intersect(Master.NewText.Lines)
.Count();

Double Filtering in LINQ

I need to join all data according to GroupIDs, which these GroupIDs are owned by EmpNo
public IEnumerable<EmployeeWithEmail> GetAllEmployeesWithEmail(int EmpNo)
{
using (var context = new SQL_TA_SCOREBOARDEntities1())
{
return (from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
//need code to join all data according to EmpNo's GroupIDs
group new
{
ea.EmpNo,
subjoin.Role,
vh.EmailAddress,
vh.LNameByFName,
ea.Active
} by vh.LNameByFName into grp
let item = grp.FirstOrDefault()
orderby item.Role ascending
select new EmployeeWithEmail
{
EmpNum = item.EmpNo ?? 0,
Role = item.Role,
EmailAddress = item.EmailAddress,
LNameByFname = item.LNameByFName,
Active2 = item.Active ?? false
}).ToList();
}
}
I guess I'm trying to filter twice and join common data, but there are actually two filters, which I don't know how to control.
So my output would be like:
EmpNo ---> __ 01 | 01 | 01 | 01
GroupID ---> __10 | 10 | 20 | 20
Data ---> _________Apple | Apple | Orange | Orange
I can filter EmpNo 01 and GroupID 10 but What if the EmpNo belongs to two groups?
Sorry for not finding the right terminologies.
Thanks in advance.
Based on your comment, the SQL you are trying to generate should be (I've simplified slightly)
SELECT EmployeeAccess.EmpNo, View_SystemAdminMembers.LNameByFName, View_SystemAdminMembers.GroupName,
View_SystemAdminMembers.Role, View_SystemAdminMembers.Active, View_SystemAdminMembers.EmpNo,
View_SystemAdminMembers.RoleID
FROM EmployeeAccess
INNER JOIN View_SystemAdminMembers ON EmployeeAccess.GroupID = View_SystemAdminMembers.GroupID
WHERE (EmployeeAccess.EmpNo = '01')
This is pretty different from what you show in your question:
from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
so I'm not sure if my answer is going to help or not, but to get the SQL you specify, I think you would want to do:
var query =
from ea in context.EmployeeAccess
join vsam in context.View_SystemAdminMembers on ea.GroupID equals vsam.GroupID
where ea.EmpNo == "01"
select new
{
ea.EmpNo, vsam.LNameByFName, vsam.GroupName, vsam.Role, vsam.Active, vsam.EmpNo, vsam.RoleID
};
Using fluent syntax (not query syntax) it would look a bit like:
var query =
context.EmployeeAccess
.Join(context.View_SystemAdminMembers, allEA => allEA.GroupID, allVSAM => allVSAM.GroupID, (ea, vsam) => new {ea, vsam})
.Where(combined => combined.ea.EmpNo == "01")
.Select(combined => combined.ea.EmpNo, combined.vsam.LNameByFName, combined.vsam.GroupName, combined.vsam.Role, combined.vsam.Active, combined.vsam.EmpNo, combined.vsam.RoleID);
(although I admit--I normally return the whole entity like
.Select(combined => combined.ea) or something like that so I'm not 100% certain on that last line...)
Note that in both cases, "var query" is going to be an IQueryable which means you will still need to add a ToList or equivalent to get your results. Before you do that, though, you'll want to apply Tim Burkhart's answer to make any modifications you want on it (like GroupBy or whatever). As he noted, one of the cool things about IQueryable is that you don't have to do it all in one statement; you can take query just as I have defined it above and then add something like
query = query.Where(c => c.LNameByFName.Contains("A"))
or whatever.
One more note--your return value is entirely made up of items from View_SystemAdminMembers with the exception of EmployeeAccess.EmpNo, but since you are filtering on that, you should already know what it is. It may be easier to return just a View_SystemAdminMember object, rather than creating a new type. That's up to you.
I'm not really following what you're asking for. Perhaps it can be rephrased or given more context or formatted differently?
But I thought I would make some suggestions that might help you get to where you are wanting to go.
1 - The function name implies that the person will provide an email and receive multiple Employee objects with that email. But it appears to accept an employee id and return a list of EmployeeWithEmail. Consider renaming the function to match what it is doing. Also consider returning IEnumerable<Employee> or IEnumerable<IEmployeeEmailView>
2 - This function is doing a lot. (And it takes more than a few seconds to figure out what it is doing). In cases like this, I would start off simple. Don't do the grouping or sorting or anything. Have some function return the results of this:
from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join
in the form of something like IEnumerable<Employee>
public class Employee {
public int Id { get; set; }
public string Role { get; set; }
public string EmailAddress { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
From there, things are easier to deal with. You're not having to work with LINQ-to-SQL. You can just do...
IEnumerable<Employee> employees = <your renamed function>();
var employeesGroupedByName = employees.GroupBy(e => e.Name);
Hopefully that makes things easier for you. It doesn't solve your answer, but I think it might make things less complicated/easier to work with.
So written out fully, it could be something like:
public class EmployeeRepository {
public IEnumerable<Employee> GetAll() {
// This function won't compile. I don't know the syntax for this type of LINQ
using (var context = new SQL_TA_SCOREBOARDEntities1()) {
return (from ea in context.View_SystemAdminMembers
join vh in context.View_HCM on (Int16)ea.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
}
}
public IEnumerable<Employee> GetAllEmployeesWithEmployeeId(int employeeId) {
return GetAll().Where(e => e.Id == employeeId).ToList();
}
public IEnumerable<Employee> SomeOtherFunctionThatDoesWhatYouWantToDoFromThisPost() {
// You could also create a class that extends IEnumerable<Employee> to
// encapsulate the logic so that the repository doesn't have a million functions
}
}
That's all I've got for you. I could give a more complete answer if the problem was spelled out a little bit better, but hopefully this gets you on the right track.

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);

How to group rows using LINQ to Entity separating values with commas?

Below is my LINQ Query, that im using to select ITEMS:
protected void Page_Load(object sender, EventArgs e)
{
whatsmydiscountEntities ctx = new whatsmydiscountEntities();
int IdRelationshipItems = Convert.ToInt32(Request.QueryString["IdRelationshipItems"]);
int IdProductService = Convert.ToInt32(Request.QueryString["IdProductService"]);
var Items = (from es in ctx.State
join ie in ctx.ItemsStates on es.StateId equals ie.StateId
join i in ctx.Items on ie.IdItem equals i.IdItem
join iir in ctx.ItemsRelationshipItems on i.IdItem equals iir.IdItem
join ir in ctx.RelationshipItems on iir.IdRelationshipItems equals ir.IdRelationshipItems
join ips in ctx.ItemsProductsServices on i.IdItem equals ips.IdItem
join ps in ctx.ProductsServices on ips.IdProductService equals ps.IdProductService
where iir.IdRelationshipItems == IdRelationshipItems
&& ips.IdProductService == IdProductService
&& ir.Active == 1
&& i.Active == 1
select new
{
ItemName = i.Name,
StateSigla = es.Sigla,
ProductServiceName = ps.Ttitle,
RelationshipItemName = ir.Name,
RelationshipItemImage = ir.Image,
RelationshipItemActive = ir.Active,
ItemSite = i.Site,
ItemDescription = i.Description,
ItemAddress = i.Address,
Iteminformationdiscount = i.information_discount,
ItemLogo = i.Logo,
ItemActive = i.Active,
StateId = ie.StateId,
IdRelationshipItems = iir.IdRelationshipItems,
IdProductService = ips.IdProductService
}).ToList();
}
As you can see, the result will be 1 row for each state, if the user passes the IdRelationshipItems and the IdProductService.
Instead of 1 row for each state with the same information, I'd like to show only 1 row and all the states separated by commas. What do I need to change to do this?
I had to solve this problem today. I have a view model that looks like this:
public class IndexViewModel
{
public string Search { get; set; }
public IPagedList<MembershipUser> Users { get; set; }
public IEnumerable<string> Roles { get; set; }
public bool IsRolesEnabled { get; set; }
public IDictionary<string,string> Tags { get; set; }
}
I needed to return a unique list of users and all of the Tags that are assigned to them as a comma separated string.
The data is stored like this:
"41FFEC0F-B920-4839-B0B5-862F8EDE25BD", tag1
"41FFEC0F-B920-4839-B0B5-862F8EDE25BD", tag2
"41FFEC0F-B920-4839-B0B5-862F8EDE25BD", tag3
And I needed output that looked something like this:
"41FFEC0F-B920-4839-B0B5-862F8EDE25BD", "tag1, tag2, tag3"
I ended up creating a List of UserId's like this (I'm using the MembershipProvider which exposes the UserId as ProviderUserKey):
var userIdList = users.Select(usr => (Guid) usr.ProviderUserKey).ToList();
The object "users" is a MembershipUser object. I then call a function in my service passing the List in like this:
Tags = _usersTagsService.FindAllTagsByUser(userIdList)
And my service function looks like this:
public IDictionary<string, string> FindAllTagsByUser(IList<Guid> users)
{
var query = (from ut in _db.UsersTags
join tagList in _db.Tags on ut.TagId equals tagList.Id
where users.Contains(ut.UserId)
select new {ut.UserId, tagList.Label}).ToList();
var result = (from q in query
group q by q.UserId
into g
select new {g.Key, Tags = string.Join(", ", g.Select(tg => tg.Label))});
return result.ToDictionary(x=>x.Key.ToString(),y=>y.Tags);
}
I'm pretty sure these two linq statements can probably be combined into one but I find it easier to read this way. Still only hits the database once.
Things to watch out for:
I was originally passing just IList users into the function FindAllTagsByUser and linq couldn't infer the type and therefore wouldn't let me use the .Contains extension method. Kept saying that linq to entities didn't support Contains.
You need to do a .ToList() on the first query to materialize it or you will get trouble from linq to entity when you try to use string.Join to create the comma separated list.
Good luck
dnash

Categories

Resources