I have an object "IdentityProvider" and "IdentityProvider" has child Domains.
class IdentityProvider
{
...
public virtual ICollection<Domain> Domains { get; set; }
...
}
class Domain
{
...
public string Name { get; set; }
...
}
There is a catch all domain called "*"
Using Linq Extensions, I need to find all the IdentityProviders that have either the specified domain, or IdentityProviders that have the catch all, but nor both.
How would I form my query?
Something like this should make it:
from i in identityProviders
let hasDomain = i.Domains.Any(d => d.Name == domainName)
let hasCatchAll = i.Domains.Any(d => d.Name == "*")
where (hasDomain && !hasCatchAll) || (!hasDomain && hasCatchAll)
select i;
You could try using XOR (^) instead in where clause:
from i in identityProviders
let hasDomain = i.Domains.Any(d => d.Name == domainName)
let hasCatchAll = i.Domains.Any(d => d.Name == "*")
where hasDomain ^ !hasCatchAll
select i;
but I'm not sure if it get's translated into SQL by your provider (you didn't specify what kind of LINQ source you're dealing with...).
Your condition can't be checked with standard LINQ functions, without iterating the Domains collection twice which is needlessly inefficient. I would use a custom filter function like this, iterating once and failing early if both are found:
identityProviders.Where(identityProvider => {
bool hasDomain = false, hasCatchAll = false;
foreach (var domain in identityProvider.Domains) {
hasDomain = hasDomain || domain.Name == domainName;
hasCatchAll = hasCatchAll || domain.Name == "*";
if (hasDomain && hasCatchAll) return false;
}
return hasDomain || hasCatchAll;
})
If you group your data by domains which have the catch all such as:
var grouped = ipProviders.Domains
.GroupBy (dm => dm.Name == "*");
Then you can either return all the catch alls at once or extract the target domains with the exact name such as
var targetDomain = "Jabberwocky";
var targets = grouped.Where (gr => gr.Key == (targetDomain == "*"))
.Select (gr => gr.Where (dm => dm.Name == targetDomain));
The grouping looks like this with data of Jabberwocky, OmegaMan and two domains with *
Thanks to those that answered, you helped me in other areas, but for this question I ended up doing the following, probably not the best way, but it works:
See if the domain exists:
var domExists = db.Domains.Any(d => d.Name == domain);
Find all the identity providers where domain exists AND domExists OR find wildcard and not domExists.
IdentityProviders.Where(d =>
d.Domains.Any(n => n.Name == domain && domExists) ||
d.Domains.Any(n => n.Name == "*" && !domExists)
).Any()
The answer you gave doesn't give you what you asked for in the question. You said you wanted providers that had one or the other but not both.
First, if a provider has both, it will be selected by this code because the first condition is true:
d.Domains.Any(n => n.Name == domain && domExists)
Second, if a provider has the catch-all but NOT the specified domain, it will not be selected if the domain does exist in a different provider. This is because domExists will be true, so the second check will fail:
d.Domains.Any(n => n.Name == "*" && !domExists)
I don't see how capturing the domExists flag can really help you. However, I think starting by searching the entire collection of domains is the right idea. You could try this:
First, collect all the IDs of providers for domains that match either "*" or the name (I assume Domain must have a foreign key to IdentityProvider):
var providerIds =
db.Domains.Where(d => d.Name == domain || d.Name == "*")
.Select(d => d.IdentityProviderID)
.ToList();
This narrows it down quite a lot, and we have a way of filtering it again: Any providers that have both will have been added to the list twice, so we just need to select all the IDs that only appear once:
var uniqueProviderIds =
providerIds.GroupBy(id => id)
.Where(g => g.Count() == 1)
.Select(g => g.Key)
.ToList();
Now uniqueProviderIds.Any() will give you your answer. You can also use this list to build another SQL query to get the actual IdentityProvider objects if you need them:
db.IdentityProviders.Where(ip => uniqueProviderIds.Contains(ip.ID)).ToList()
please try
identityProviders.Where(ip=>ip.Domains.Any(d=>d.Name=="SearchDomain" || d.Name=="*"))
Related
As shown in the below code, the API will hit the database two times to perform two Linq Query. Can't I perform the action which I shown below by hitting the database only once?
var IsMailIdAlreadyExist = _Context.UserProfile.Any(e => e.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = _Context.UserProfile.Any(x => x.Username == myModelUserProfile.Username);
In order to make one request to database you could first filter for only relevant values and then check again for specific values in the query result:
var selection = _Context.UserProfile
.Where(e => e.Email == myModelUserProfile.Email || e.Username == myModelUserProfile.Username)
.ToList();
var IsMailIdAlreadyExist = selection.Any(x => x.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = selection.Any(x => x.Username == myModelUserProfile.Username);
The .ToList() call here will execute the query on database once and return relevant values
Start with
var matches = _Context
.UserProfile
.Where(e => e.Email == myModelUserProfile.Email)
.Select(e => false)
.Take(1)
.Concat(
_Context
.UserProfile
.Where(x => x.Username == myModelUserProfile.Username)
.Select(e => true)
.Take(1)
).ToList();
This gets enough information to distinguish between the four possibilities (no match, email match, username match, both match) with a single query that doesn't return more than two rows at most, and doesn't retrieve unused information. Hence about as small as such a query can be.
With this done:
bool isMailIdAlreadyExist = matches.Any(m => !m);
bool isUserNameAlreadyExist = matches.LastOrDefault();
It's possible with a little hack, which is grouping by a constant:
var presenceData = _Context.UserProfile.GroupBy(x => 0)
.Select(g => new
{
IsMailIdAlreadyExist = g.Any(x => x.Email == myModelUserProfile.Email),
IsUserNameAlreadyExist = g.Any(x => x.Username == myModelUserProfile.Username),
}).First();
The grouping gives you access to 1 group containing all UserProfiles that you can access as often as you want in one query.
Not that I would recommend it just like that. The code is not self-explanatory and to me it seems a premature optimization.
You can do it all in one line, using ValueTuple and LINQ's .Aggregate() method:
(IsMailIdAlreadyExist, IsUserNameAlreadyExist) = _context.UserProfile.Aggregate((Email:false, Username:false), (n, o) => (n.Email || (o.Email == myModelUserProfile.Email ? true : false), n.Username || (o.Username == myModelUserProfile.Username ? true : false)));
I'm trying to recreate this SQL query in LINQ:
SELECT *
FROM Policies
WHERE PolicyID IN(SELECT PolicyID
FROM PolicyRegister
WHERE PolicyRegister.StaffNumber = #CurrentUserStaffNo
AND ( PolicyRegister.IsPolicyAccepted = 0
OR PolicyRegister.IsPolicyAccepted IS NULL ))
Relationship Diagram for the two tables:
Here is my attempt so far:
var staffNumber = GetStaffNumber();
var policyRegisterIds = db.PolicyRegisters
.Where(pr => pr.StaffNumber == staffNumber && (pr.IsPolicyAccepted == false || pr.IsPolicyAccepted == null))
.Select(pr => pr.PolicyID)
.ToList();
var policies = db.Policies.Where(p => p.PolicyID.//Appears in PolicyRegisterIdsList)
I think I'm close, will probably make two lists and use Intersect() somehow but I looked at my code this morning and thought there has to be an easier way to do this,. LINQ is supposed to be a more readble database language right?
Any help provided is greatly appreciated.
Just use Contains:
var policies = db.Policies.Where(p => policyRegisterIds.Contains(p.PolicyID));
Also better store policyRegisterIds as a HashSet<T> instead of a list for search in O(1) instead of O(n) of List<T>:
var policyRegisterIds = new HashSet<IdType>(db.PolicyRegisters......);
But better still is to remove the ToList() and let it all happen as one query in database:
var policyRegisterIds = db.PolicyRegisters.Where(pr => pr.StaffNumber == staffNumber &&
(pr.IsPolicyAccepted == false || pr.IsPolicyAccepted == null));
var policies = db.Policies.Where(p => policyRegisterIds.Any(pr => pr.PolicyID == p.PolicyID));
The code below is working, but if it can't find an entry with "Domain Administrator" as per the where clause it will completely ignore anything else on that particular result. This leads to me missing items, as the view I am generating may not have a "Domain Administrator" entry. I understand that this is because it is an explicit where, but I'm not quite sure how to represent exactly what I need to do. I suspect I need to do a LEFT JOIN, but not sure how this then effects the navigation properties. Any guidance into the right direction would be appreciated.
var endpointConstructor = db.tbl_equipment.Include(t => t.tbl_Backup_Configuration)
.Where(e => e.tbl_Backup_Configuration.FirstOrDefault().BackupType == null)
.Where(e => e.tbl_customer.Calc_Contract_Status == true && e.Calc_Contract_Status == true && e.Equip_type.Contains("server")).OrderBy(e => e.tbl_customer.Priority)
.Where(what => what.tbl_customer.tbl_user_pass_list.FirstOrDefault().Usage1 == "Domain Administrator")
.Select(s => new CompanyServerUserPassViewModel { Comp_ID = s.Comp_ID, ServerName = s.NetBIOS_name, AdminUsername = s.tbl_customer.tbl_user_pass_list.FirstOrDefault().Username,
AdminPassword = s.tbl_customer.tbl_user_pass_list.FirstOrDefault().Password, Company = s.Company, TeamviewerID = s.tbl_computerinfo.FirstOrDefault().teamviewerID });
Something along the lines of:
.Where(what => [statement that evaluates to true if there is no domain admin]
|| what.tbl_customer.tbl_user_pass_list.First().Usage1 == "Domain Administrator")
I need to check for duplicate entries before saving entity to the database. Below is my current code
if (db.Product.Any(x => x.Code == entity.Code))
{
error.Add('Duplicate code');
}
if (db.Product.Any(x => x.Name == entity.Name))
{
error.Add('Duplicate name');
}
if (db.Product.Any(x => x.OtherField == entity.OtherField))
{
error.Add('Duplicate other field');
}
The problem with code above is that it made 3 db call to validate entity. This table has millions of record and this app will be used by thousand users. So it will hurt the performance badly. I could make it one query though
if (db.Product.Any(x => x.Code == entity.Code || x.Name == entity.Name || x.OtherField == entity.OtherField))
{
error.Add('Duplication found');
}
The problem with the second code is that i wouldnt know which field is duplicate.
What is the better way of doing this? Should i depend only on unique constraint in the database? However error from the database is ugly.
EDIT
I need to show all errors to the user if more than 1 duplicate fields.
Consider the scenario: if the duplicate fields are code and name. If i tell the user that the code already exists, then he changes the code and try to save it again. Then the second error (name field) shows. It makes the user hit save for a couple of times before successfully saving it.
If you have indexes on the fields Name, Code, and OtherField, then duplicate checking will not too long, but will still be 3 calls to the database instead of 1.
The usual solution in this case is the counting of duplicates. Then if count is equals to 0, there isn't duplicates.
Here you'll find some hacks to do it.
Short example:
var counts =(
from product in db.Products
group product by 1 into p
select new
{
Name = p.Count(x => x.Name == name),
Code = p.Count(x => x.Code == code),
OtherField = p.Count(x => x.OtherField == otherFields)
}
).FirstOrDefault();
if (counts.Name > 0)
error.Add("Duplicate name");
if (counts.Code > 0)
error.Add("Duplicate code");
Update: it's seems that it's possible to solve the problem even more simple method:
var duplicates =(
from product in db.Products
group product by 1 into p
select new
{
Name = p.Any(x => x.Name == name),
Code = p.Any(x => x.Code == code),
OtherField = p.Any(x => x.OtherField == otherFields)
}
).FirstOrDefault();
if (duplicates.Name)
error.Add("Duplicate name");
You can do something like this:
string duplicateField;
bool validationResult = db.Product.Any(x => {
if(x.Code == entity.Code){
duplicateField = "Code";
return true;
}
// Other field checks here
}
if(validationResult){
// Error in field <duplicateField>
}
1- You can select duplicate entity
var product = db.Product.FirstOrDefault(x => x.Code == entity.Code
|| x.Name == entity.Name
|| x.OtherField == entity.OtherField);
if (product == null)
;//no duplicates
if (product.Code == entity.Code)
{
error.Add('Duplicate code');
}
if (product.Name == entity.Name)
{
error.Add('Duplicate name');
}
if (product.OtherField == entity.OtherField)
{
error.Add('Duplicate other field');
}
2- You can create stored procedure for insert and check for duplicates in it;
EDIT:
OK, you can write something like this
var duplicates = (from o in db.Products
select new
{
codeCount = db.Products.Where(c => c.Code == entity.Code).Count(),
nameCount = db.Products.Where(c => c.Name == entity.Name).Count(),
otherFieldCount = db.Products.Where(c => c.OtherField == entity.OtherField).Count()
}).FirstOrDefault();
This will select number of each duplicate by fields.
One thing to note: you should have unique constraints in database anyway, because while u validating and saving data, another row with these values may be inserted before u insert them.
I have the following linq:
objfl = db.tblFl.First(t => t.sp == id && t.ProgID == sPgm);
I like to also order by id but not sure how to do this. I tried a number of different ways but was not successful
As suggested by BrokenGlass, if you want to filter by ProgID, sort by sp and retrieve the first item:
db.tblFl.Where(t => t.ProgID == sPgm)
.OrderBy(t => t.sp)
.First()
Try this
objfl = db.tblFl.Where(t => t.sp == id && t.ProgID == sPgm).OrderBy(t => t.sp);