Single field extraction from a database using lambda - c#

My problem is connected to the extraction method called lambda ( I am using it in C# ). I have a database and in it I have a table called Students and I wonder if I would be able to extract the firstName and lastName of certain student whose ID matches my search.
I have tried a few things so far without any luck.
A bit of code from my last attempt :
var studentName =
context.Student //context is the variable with the entities and Student is the table
.Where(student => student.StudentID == SomeInt) // extract only the one with the chosen ID
.Select(student => student.FirstName) // tryng only to select the wanted field(s)
.ToString() // since I want it to be displayed in a textbox

Currently you're calling Select on an IQueryable<string> - your query represents a sequence of student first names, even if it only matches a single one.
If you only want a single name, you can use:
var studentName =
context.Student
.Where(student => student.StudentID == SomeInt)
.Select(student => student.FirstName)
.FirstOrDefault();
Then assuming FirstName is a string, you don't need ToString() at all. The value will be null if there's no student with that ID. Alternatively, you could use Single(), SingleOrDefault(), First(), Last() or LastOrDefault(). So for example, you'd then use:
if (studentName == null)
{
// No match found. Return an error or whatever.
}
else
{
// Use studentName
}
(You should only use FirstOrDefault() if you then check for nullity, or pass it on to something else that does so. Otherwise you can end up with a NullReferenceException later which is hard to diagnose.)
If you want both the first and last names, you need to change your projection. For example:
var studentName =
context.Student
.Where(student => student.StudentID == SomeInt)
.Select(student => new { student.FirstName, student.LastName })
.FirstOrDefault();
Now the type of studentName will be an anonymous type - again, it'll be null if there are no matches, but otherwise it will have appropriate FirstName and LastName properties.

You can write something like:
var name = context.Students.FirstOrDefault(c=>c.Id == SomeId).FirstName;

You could try something like this:
var student = context.Student
.Where(student => student.StudentID == studentId)
.Select(student => new
{
FirstName = student.FirstName,
LastName = student.LastName
}).FirstOrDefault();
If any student with studentId exists at all, then you get her first name and her last name, like below:
var firstName = student.FirstName;
var lastName = student.LastName;
If there isn't any student with the given studentId, the result of the above query, would be null. Hence, before you use the dot notation, please validate that the user you are looking for has been found like below:
if(student!=null)
{
// Here you can access both the LastName and
// the FirstName of the found student.
}

Related

IEnumerable.Except() on LINQ doesn't run between ideal source and target lists

I start my question by mentioning this first - I have gone through the other SO questions and did end up into a situation/problem which I could not find myself an answer. So if there is, please point me to that.
My problem:
I have two lists of model objects.
Consider, I have a model class -
public class Contact
{
public string FirstName {get;set;}
public string LastName {get;set;}
public string MiddleName {get;set;}
public long ContactId {get;set;}
public long? DestKey {get;set;}
}
And i have two data sources that may have some contacts data. Imagine, from Db source 1, I have 2 contacts and from Db Source 2, i have 10 contacts.
I am trying to find the unique contacts from Db1 list that are not on the Db2 list. I do use a custom Equality comparer to compare the data by checking the FirstName and Lastname fields. I did override the GetHashCode() as well.
So, my custom Equality Comparer looks like below:
public class MyContactComparer : IEqualityComparer<Contact>
{
public bool Equals(Contact src, Contact dest)
{
// compare LastName
if (!src.LastName.Equals(dest.LastName, StringComparison.CurrentCultureIgnoreCase)) return false;
// if LastName matches, compare FirstName
if (!src.FirstName.Equals(dest.FirstName, StringComparison.CurrentCultureIgnoreCase))
if (!(src.FirstName.Contains(dest.FirstName, StringComparison.CurrentCultureIgnoreCase) ||
dest.FirstName.Contains(src.FirstName, StringComparison.CurrentCultureIgnoreCase)))
return false;
// do other needful comparisons
//TODO: check for other comparison
return true;
}
public int GetHashCode(MmdContact obj)
{
return obj.FirstName.GetHashCode() ^ obj.LastName.GetHashCode();
}
}
and I call this by,
var nonMatchingContactsList = db2srcModelleddb1Data
.Except(db2ContactsData.ToArray(), new MyContactComparer())
.ToList()
.Select(person => person.ContactId);
Now, I have the data on Db1 set as
{FirstName = "Studo Mid", LastName = "Tar", MiddleName = null, ContactId = 1}
{FirstName = "Foo", LastName = "Bar", MiddleName = "H", ContactId = 2}
Data on Db2 set as,
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10001}
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10002}
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10003}
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10004}
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10005}
{FirstName = "Studo", MiddleName = "Mid", LastName = "Tar", DestKey = 10006}
...
and so on,
by having duplicate records by names but having a unique DestKey. Assume, they were created by the logic that I explained below ending up in dups. Irrespective of that data quality fact, I'd expect the 2 contacts from Db1 set, be compared against the 10 contacts on Db2 set.
But when I debug this, the Equals() method is just iterating and checking between the 10 contacts of Db2 set as I could see the DestKey values between 'src' and 'Dest'. It seems to me that its comparing within the Db2 set and then identifying the 2 contacts on Db1 as not exists. So my logic goes and creates them, upon which, the "Studo Mid Tar" record is getting created again and again.
As and when I rerun again, it doesn't detect that contact as matching and doesn't do the Except() part. I'd say, the 2nd contact (Foo Bar) on the Db1 is something i'd like to see as the output to be created. The GetHashCode() is happening against the db2 sets only.
So, what is going wrong and why is this behavior?
What is needed to run this against the appropriate lists, i.e, 2 vs 10 records
UPDATE:
My primary question is lying around why the Equals() is comparing with its own list? Take a look at this fiddle - https://dotnetfiddle.net/upCgbb
I see the desired output but what i dont get it is, why the Equals() method compares the data of same Model type (in this case DataB) for few iterations instead of comparing A vs B? It does compares 1001 with 1002, then 1001 with 1003 before it is comparing with the actual A ContactId 1. Thats my question on why would it compare its own list?
If you need to compare two lists and you have some kind of one-to-many relationship (one-to-none is also an one-to-many) or left-join you should use .GroupJoin().
So if you replace your worker method with this:
public void DoMyWork()
{
// expecting 1 record from A which is ContactId = 2 {Foo Bar}
var nonMatchingContactsList = dataFromA
.GroupJoin(
dataFromB,
a => $"{a.FirstName}|{a.LastName}",
b => $"{b.FirstName} {b.MiddleName}|{b.LastName}",
(a, matchingBs) => matchingBs.Any() ? null : a)
.Where(a => a != null)
.ToList();
Console.WriteLine($"Total contacts specific to DbA: {nonMatchingContactsList.Count()}\r\n{string.Join("\n", nonMatchingContactsList)}");
}
You'll get your desired answer without usage of any self-written comparer. The magic happens within these lines:
a => $"{a.FirstName}|{a.LastName}"
This will create the desired key from your class. In my mental model this gets something like a Dictionary<string, ModelA>.
b => $"{b.FirstName} {b.MiddleName}|{b.LastName}"
This does the same for your second class and uses the needed properties to generate a similar key to make the comparison. Depending on your data you maybe have to improve this method to remove the space, if the middle name is null or empty.
(a, matchingBs) => matchingBs.Any() ? null : a
This is the result selector of the group join method. The first parameter is a modelA object and the second parameter is a list of all matching modelB objects. Because you like to get all ModelA objects where none matching objects exists in the ModelB list, we check matchingBs.Any() and if there is something matching, we return null otherwise a. Afterwards you simply have to throw away all the null values and you get the desired items from your ModelA list.
Maybe better method to avoid space if middle name is empty:
b => string.isNullOrEmpty(b.MiddleName)
? $"{b.FirstName}|{b.LastName}"
: $"{b.FirstName} {b.MiddleName}|{b.LastName}"
One last tip about writing your own comparer class. There are a bunch of rules to respect, when implementing the methods GetHashCode() and Equals(). To match them all, you should use this blue-print as a starting point and adopt to the properties that should be compared. By using this blue-print you avoid a lot of problems:
public bool Equals(MyObject x, MyObject y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.MostDifferentialProperty != y.MostDifferentialProperty)
return false;
if (x.DifferentialProperty != y.DifferentialProperty)
return false;
// Other properties to compare...
return true;
}
public int GetHashCode(MyObject obj)
{
if (ReferenceEquals(obj, null))
return -1;
// Add all properties from Equals() method here.
return HashCode.Combine(obj.MostDifferentialProperty, obj.DifferentialProperty);
}

GroupBy with different parameters

I have one query with groupBy parameters depends on the user input. If the condition is true, query will be grouped with DOB attribute. else, it doesnt need to.
Here is my code
var userList = user.GroupBy(x => new { x.Name, x.Age});
if (isBaby)
{
userList = user.GroupBy(x => new { x.Name, x.Age, x.DOB });
}
but i got an error which the parameters is not same. The latter code is to select from this query like this.
var allList= userList.Select({
...
}).ToList();
I dont want to create two select list because it is easier to manage if only use one select list.
Edited:
This is the error
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age>, Domains.User>>' to 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age, string DOB>, Domains.User>>'
enter code here
Assuming DOB is a DateTime:
var userList = user.GroupBy(x => new { x.Name, x.Age, dob = isBaby ? x.DOB : DateTime.MinValue });
That way the expression is always the same type, but won't cause an additional tier of grouping when isBaby is false.
Well you are constructing two different results in the group by...
Var implicitly tries to assume the type, and the first assignment is an anon type with only Name and Age. The second one adds DOB which is a different anon type.
Something like
new { x.Name, x.Age, DateOfBirth = isBaby ? x.DOB : DateTime.MinValue }
Would fix it

C# linq how to get property from predicate list when condition is met

I have the following linq query
var usersToNotify = trainingUsers.Where(x => delegatesToBeReminded.Any(d => d.UserGuid == x.UserGuid))
.Select(x => new RecipientDetail
{
FullName = x.FullName,
Email = x.Email,
// get property from delegatesToBeReminded
})
.ToList();
In the example above, i have trainingusers and delegatesToBeReminded list. i want to retrieve the matching record found in trainingusers and create custom type, with fullname, email from trainingusers and additional property from delegatesTobeReminded.
Can anyone help me how to do this?
Can i use something like this?
var x = from tu in trainingUsers
join d in delegatesToBeReminded on tu.UserGuid equals d.UserGuid
select new RecipientDetail
{
FullName = tu.FullName,
Email = tu.Email,
Session = d.Session
};
Thanks
Easiest would be to use a join, as you suggested:
trainingUsers.Join(
delegatesToBeReminded,
user => user.UserGuid,
delegateToBeReminded => delegateToBeReminded.UserGuid,
(user, delegateToBeReminded) => new RecipientDetail
{
FullName = user.FullName,
Email = user.Email,
Delegate = delegateToBeReminded,
});
(Or you can write the equivalent in linq query syntax, as you did).
Another way is to rewrite this in linq query syntax, using let:
from user in trainingUsers
let delegateToBeReminded = delegatesToBeReminded.FirstOrDefault(d => d.UserGuid == user.UserGuid)
where delegateToBeReminded != null
select new RecipientDetail
{
FullName = user.FullName,
Email = user.Email,
Delegate = delegateToBeReminded,
}
Note that these differ depending on what happens if there is more than one delegate for a particular user. The first creates a new RecipientDetail object for each user/delegate pair; the second creates a RecipientDetail object per user, and picks the first delegate.

dynamically define key name for which the value needs to be identified

I have a keyvalue pair list some thing like this
List<Subscriptions> subs = new List<Subscriptions>();
subs.Add(new Subscriptions() { Id = 1, Name = "ABC" });
subs.Add(new Subscriptions() { Id = 1, Name = "DEF" });
I can search against one key (ID or Name) but what I want to achieve is that user define which key they want to search against ID or Name
right now i am using this approach to filter the list based on Name Value
var filtered = subs.Where(sub => sub.Name.IndexOf(SearchString.Text,StringComparison.OrdinalIgnoreCase) >=0);
sub.Name is defined statically here, I want the user to choose what they want their search to be based on
for example if we have abc:Name program search for abc under Name key and if we have 1:Id then it search for 1 in ID.
This is just an example , in real scenario i can have multiple fields in my list.
I hope I am able to make myself clear.
Why you don't apply the Where by an simple if else or switch
// keyword : abc:Name or 1:Id
var value = keyword.Split(':')[0];
var key = keyword.Split(':')[1];
if(key == "Name")
{
var filterred = subs.Where(sub => sub.Name == value);
}
else if(key == "Id")
var filterred = subs.Where(sub => sub.id == int.Parse(value));
}
Fast answer:
string name = "";
int? id = null;
List<Subscriptions> subs = new List<Subscriptions>();
var query = subs.AsQueryable();
if (!string.IsNullOrEmpty(name))
query = query.Where(p => p.Name == name);
if (id.HasValue)
query = query.Where(p => p.id == id.Value);
var result = query.ToArray();
Detailed answer: you can read about expression tree and IQueryable interface
Basically you can avoid to cast your list to IQueryable if you not use something like Entity Frmework or OData. But if you need to convert you LINQ expression to something more complex - you should use IQueryable, or build your own expression tree.

Unwieldy LINQ statement with multiple search criteria

I have a form with multiple search criteria that a user can use to search for employee data, e.g. FirstName, LastName, HireDate, Department, etc.
I am using LINQ and am wondering what method could I use to query a collection of Employes given any of of the search criteria, i.e. a user does not have to enter all, but they do have to enter at least one of the search parameters.
So far, while testing my LINQ statement with two search parameters in place, it seems that I have to see if the search parameter is entered or not.
If this is the case, then this can get quite unwieldy for many search parameters.
// only FirstName is entered
if (!string.IsNullOrEmpty(FirstName) && string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.FirstName.Contains(fName));
}
// only LastName is entered
else if (string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.LastName.Contains(lName));
}
// both parameters are entered
else if (!string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.FirstName.Contains(fName))
.Where(emp => emp.LastName.Contains(lName));
}
FYI, I initially thought that I could just append Where() statements to my LINQ statement with the pertinent search parameters but I noticed that not all records were being returned that should and thus the above logic of if-then statements.
What about something like this:
IQueryable<Employee> employees = DB.Employees;
if (!string.IsNullOrEmpty(FirstName))
{
employees = employees
.Where(emp => emp.FirstName.Contains(fName));
}
if (!string.IsNullOrEmpty(LastName))
{
employees = employees
.Where(emp => emp.Last.Contains(lName));
}
You can write it like this:
var employees = DB.Employees.AsQueryable();
if (!string.IsNullOrEmpty(fName)
employees = employees.Where(emp => emp.FirstName.Contains(fName));
if (!string.IsNullOrEmpty(lName)
employees = employees.Where(emp => emp.LastName.Contains(lName));
I encountered a similar challenge where a user could select 0, 1 or many values for about 10 searchable fields and needed to construct that query at runtime.
I ended up using LINQKit:
http://www.albahari.com/nutshell/linqkit.aspx
In particular I used it's predicate builder, which is described here:
http://www.albahari.com/nutshell/predicatebuilder.aspx
In your example above, you've encompassed the query multiple within if statements.
The alternative is to build the query as you go.
If you were to declare var employees = DB.Employees outside of those if statements (Assuming that it's always relevant), then you could just tack on your where statements within your if statements if they're applicable.
LINQ gives you deferred execution, so you don't have to have the entire expression in a single block (Even though it feels most natural to do so and in many cases you will).
Things get a bit more complicated if you want to mix in OR's with ANDs, but that's where the previously mentioned predicate builder comes in.
Unfortunately I don't have any examples to share, but those links should get you off to a good start.
var resultData = (from data in db.Abc
where !string.IsNullOrEmpty(firstName) ? data.FirstName == firstName : true
&& data.UserType == userTypeValue
&& !string.IsNullOrEmpty(lastName) ? data.LastName == lastName : true
&& !string.IsNullOrEmpty(gender) ? data.Gender == gender : true
&& !string.IsNullOrEmpty(phone) ? data.CellPhone == phone : true
&& !string.IsNullOrEmpty(fax) ? data.Fax == fax : true
&& !string.IsNullOrEmpty(emailAddress) ? data.Email == emailAddress : true
&& !string.IsNullOrEmpty(address1) ? data.Address == address1 : true
select new
{
UserName = data.UserName,
FirstName = data.FirstName,
Address = data.Address,
CellPhone = data.CellPhone,
Fax = data.Fax,
Email = data.Email
}).ToList();

Categories