C# - Method with Generic Property - c#

I've seen plenty of examples of how to use generic Objects in C#, but can I have a generic property for a static object?
I have 2 distinct lists of Providers:
List<Providers> OldProviders
List<Providers> NewProviders
OldProviders has providers from the database, and NewProviders has providers the user has selected in the View. If any OldProviders aren't in NewProviders, I want to remove them from the database. If any NewProviders aren't in OldProviders, I want to add them to the database. Simple.
One problem:
Provider has 2 properties that correspond to foreign keys important for comparison: StaffID and LicenseID (both integers)
Depending on the choice the user makes, I only care about one of these IDs.
Currently I have 2 sets of helper methods which are completely identical (6 total helpers), except for whether they compare using StaffID or LicenseID. It technically works, but I don't want to maintain it.
Here's an example:
private bool DeleteOldStaffProviders(List<Provider> oldSelectedStaff, List<Provider> newSelectedStaff)
{
foreach (var oldSelected in oldSelectedStaff)
{
bool remove = newSelectedStaff.SingleOrDefault(n => n.StaffID == oldSelected.StaffID) == null;
if (remove)
{
//<remove from database, return false if failure>
}
}
return true;
}
Is there a way I can rewrite this method with a generic parameter reference?
i.e.
newSelectedStaff.SingleOrDefault(n => n.T == oldSelected.T)
where T is either LicenseID or StaffID.
I've seen a lot of examples of people using LINQ and reflection for things kinda similar to this, but I can't seem to figure it out. If you could baby me through it, that would be appreciated.

Yes, use another parameter of type Func<Provider,int> to act as the selector:
private bool DeleteOldStaffProviders(List<Provider> oldSelectedStaff, List<Provider> newSelectedStaff, Func<Provider,Provider,bool> selector)
{
foreach (var oldSelected in oldSelectedStaff)
{
bool remove = newSelectedStaff.SingleOrDefault(n => selector(n) == selector(oldSelected)) == null;
if (remove)
{
//<remove from database, return false if failure>
}
}
return true;
}
usage
DeleteOldStaffProviders(oldStaff, newStaff, x => x.StaffID);
DeleteOldStaffProviders(oldStaff, newStaff, x => x.LicenseID);
You could do it another way - but I think its more clunky. Pass in a Func<Provider,Provider,bool> like this:
DeleteOldStaffProviders(oldStaff, newStaff, (o,n) => o.StaffID == n.StaffID);
And change a line to:
bool remove = newSelectedStaff.SingleOrDefault(n => selector(n,oldSelected))

Related

Setting value of object property in List<T> using LINQ

I have a List<Email>() and my Email object looks like this:
public class Email
{
public string EmailAddress { get; set; }
public bool IsPrimary { get; set; }
}
When I add a new email address that is set as primary, I want to set all the others as non-primary. I currently handle this using a foreach. Can I handle this using LINQ?
My current code is:
foreach (var item in emails)
{
if(item.EmailAddress.ToLower() != newEmailAddress.ToLower() && item.IsPrimary)
item.IsPrimary = false;
}
Linq queries collections, it doesn't modify them. The only spot in this equation that linq would come into play is actually making it a part of the enumeration - filtering the collection you're iterating over rather than doing an if statement inside it.
foreach (var item in emails.Where(e => e.IsPrimary && !e.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase)))
{
item.IsPrimary = false;
}
EDIT: I didn't originally include it as it's not LINQ and that's what the question is about, but as mentioned in the comments on your question List<T> does include a ForEach method.
It would look like this:
emails.ForEach(item =>
{
item.IsPrimary = item.IsPrimary && item.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase);
});
LINQ is intended for querying and not modification. Having said that, there is a List.ForEach operator, but with no increase in readability most of the time.
Having said that, I personally prefer not having side effect causing code that modifies the collection but I am not opposed to modifying the objects in the collection.
Add an extension method on IEnumerable to encapsulate the foreach loop:
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) {
foreach (var s in source)
action(s);
}
Then you can re-write your code as follows:
emails.Where(item => item.IsPrimary && !item.EmailAddress.Equals(newEmailAddress, StringComparison.InvariantCultureIgnoreCase))
.ForEach(item => item.IsPrimary = false);
(Thanks to #McAden for the better string comparison I always forget.)
However, since you are creating a race condition anyway, if practical I would suggest reversing your order of operations:
// before adding newEmailAddress
emails[emails.FindIndex(item => item.IsPrimary)].IsPrimary = false; // add error handling if it is possible no `IsPrimary` exists.
// now assign the newEmailAddress and set that item.IsPrimary to true
You can easily do that, but you should not as no one would expect LINQ code to modify the items in the collection.
emails
.Where(item =>
(item.EmailAddress.ToLower() != newEmailAddress.ToLower() && item.IsPrimary)
.Select(item => { item.IsPrimary = false; return true;})
.All();
Note that since LINQ queries are actually executed when result is enumerated you need something that will actually enumerate result. I.e. .All() call.
What would happen after you write this code - someone (or you in a week) remove that stupid and pointless .All() call at the end and things will be somewhat
ok, but modification no longer happen, person will spend a day sorting it out and then use some words to describe author of the code. Don't go there.

Using Contains on an List in a different way

I have this code which i want to change:
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Contains(path)) {
MyLog.WriteToLog("Folder not Found: "+path.Name + "in New Folder. ",MyLog.Messages.Warning);
currNoPairs.Add(path);
}
}
In the If part i don't want to check the path i want to check the path.Name.
So how can i use the Contains method on the properties.
the goal is to sort out all folders that have not the same name in the list of Current Directory List and New Directory List.
See - IEnumerable<T>.Contains with predicate
Those functions that take "predicates" (boolean functions that signify a match) will let you do more complex checks. In this case, you can use them to compare sub-properties instead of the top-level objects.
The new code will look something like this:
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Any(newDir => newDir.Name == path.Name)) {
// TODO: print your error message here
currNoPairs.Add(path.Name);
}
}
In reply to your comment:
Okay i understood, but whats the diffrence between any and contains then?
List<T>.Contains
This method goes through each item in the list, seeing if that item is equal to the value you passed in.
The code for this method looks a little like this (simplified here for illustration):
for(var item in yourList) {
if(item.Equals(itemYouPassedIn) {
return true;
}
}
return false;
As you see, it can only compare top-level items. It doesn't check sub-properties, unless you are using a custom type that overrides the default Equals method. Since you're using the built in DirectoryInfo types, you can't override this Equals behavior without making a custom derived class. Since there's easier ways to do this, I wouldn't recommend this approach unless you need to do it in a ton of different places.
IEnumerable<T>.Any
This method goes through each item in the list, and then passes that item to the "predicate" function you passed in.
The code for this method looks a little like this (simplified for illustration):
for(var item in yourList) {
if(isAMatch(item)) { // Note that `isAMatch` is the function you pass in to `Any`
return true;
}
}
return false;
Your predicate function can be as complicated as you want it to be, but in this case, you'd just use it to check if the sub-properties are equal.
// This bit of code defines a function with no name (a "lambda" function).
// We call it a "predicate" because it returns a bool, and is used to find matches
newDir => newDir.Name == path.Name
// Here's how it might look like if it were defined as a normal function -
// this won't quite work in reality cause `path` is passed in by a different means,
// but hopefully it makes the lambda syntax slightly more clear
bool IsAMatch(DirectoryInfo newDir) {
return newDir.Name == path.Name;
}
Since you can customize this predicate every place that you use it, this could be a better tactic. I'd recommend this style until you are doing this exact check in a bunch of places in your code, in which case a custom class might be better.
Here is how you check for property Any
foreach (DirectoryInfo path in currDirs) {
if (!newDirs.Any(dir => dir.FullName == path.FullName)) {
MyLog.WriteToLog("Folder not Found: "+path.Name + "in New Folder. ",MyLog.Messages.Warning);
currNoPairs.Add(path);
}
}
And by the way, your code could be written in a better way like this
var currDirsConcrete = currDirs.ToArray();
var pathsNotFound = "Following paths were not found \r\n " + string.Join("\r\n", currDirsConcrete.Where(d => d.FullName != path.FullName).ToArray());
var pathsFound = currDirsConcrete.Where(d => d.FullName == path.FullName).ToArray();
MyLog.WriteToLog(pathsNotFound, MyLog.Messages.Warning);
Note: You can skip the first line currDirsConcrete if your currDirs is already an array or a list. I did this to avoid redetermining the enumerable.
I would use linq with except and implement a DirComparator
List<DirectoryInfo> resultExcept = currDirs.Except(newDirs, new DirComparator()).ToList();
Here the IEqualityComparer<DirectoryInfo>:
public class DirComparator : IEqualityComparer<DirectoryInfo> {
public bool Equals(DirectoryInfo x, DirectoryInfo y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.Name.equals(y.Name);
}
public int GetHashCode(DirectoryInfo dir)
{
//Check whether the object is null
if (Object.ReferenceEquals(dir, null)) return 0;
//Get hash code for the Name field if it is not null.
return dir.Name == null ? 0 : dir.Name.GetHashCode();
}
}
you could also use intersect if you want it the other way around.

How can I simplify this LINQ code

Basically I have a multiselect list box in MVC and when the user changes selection it will come back and it should update the model. the below code works but I am just wondering how can I put it in a single foreach loop or is there a better way of updating the selection? Note: There is a many to many relationship between artist and artist type.
foreach (var artistTtype in this._db.ArtistTypes.ToList().Where(artistTtype => artist.ArtistTypes.Contains(artistTtype)))
{
artist.ArtistTypes.Remove(artistTtype);
}
foreach (var artistTtype in this._db.ArtistTypes.ToList().Where(artisttype => vm.SelectedIds.Contains(artisttype.ArtistTypeID)))
{
artist.ArtistTypes.Add(artistTtype);
}
This for adding (just use AddRange):
artist.ArtistTypes.AddRange(this._db.ArtistTypes
.Where(artisttype => vm.SelectedIds.Contains(artisttype.ArtistTypeID)));
This for removing (use ForEach):
this._db.ArtistTypes
.Where(artistTtype => artist.ArtistTypes.Contains(artistTtype)).ToList()
.ForEach(x=>artist.ArtistTypes.Remove(x));
EDIT:
you can always set
artist.ArtistTypes = this._db.ArtistTypes
.Where(artisttype => vm.SelectedIds.Contains(artisttype.ArtistTypeID)).ToList();
this will set ArtistTypes to what you want, you don't need to delete then add.
I see two "fixes":
1) You don't need to care about what's inside the list, since you're going to update the list of selections you can start from scratch, so the removal part becomes
artist.ArtistTypes.Clear();
2) Now you fill the list again. ToList() should not be needed since you're performing a .Where() to get the data, and you can leverage Linq's lazy mechanisms so you'll only read the data you actually use. You can also split the lines for increased readability (it doesn't matter: until you do the foreach() the db will not be actually hit.
//note that the .ToList() is gone
var query = this._db.ArtistTypes.Where(artisttype => vm.SelectedIds.Contains(artisttype.ArtistTypeID);
foreach (var artistTtype in query))
{
artist.ArtistTypes.Add(artistTtype);
}
2b) (UNTESTED, off the top of my head) Another way of implementing the comparison you do is through a custom IEqualityComparer, switching to .Intersect() method. This is way more solid since if your keys change in the model you only have to change the comparer.
// I'm making up "ArtistType", fix according to your actual code
class ArtistTypeEqualityComparer : IEqualityComparer<ArtistType>
{
public bool Equals(ArtistType x, ArtistType y)
{
if (ArtistType.ReferenceEquals(x, null)) return false;
if (ArtistType.ReferenceEquals(y, null)) return false;
if (ArtistType.ReferenceEquals(x, y)) return true;
return x.ArtistTypeId.Equals(y.ArtistTypeId);
}
public int GetHashCode(ArtistType obj)
{
return obj.ArtistTypeId.GetHashCode();
}
}
// And then the "add" part simplifies
artist.ArtistTypes.AddRange(this._db.ArtistTypes.Intersect(vm.SelectedIds.Select(x => new ArtistType{ ArtistTypeId = x }));

LINQ to SQL where clause verifying string contains list element

I'm using a view returning Domains according to an id. The Domains column can be 'Geography' or can be stuffed domains 'Geography,History'. (In any way, the data returned is a VARCHAR)
In my C# code, I have a list containing main domains:
private static List<string> _mainDomains = new List<string>()
{
"Geography",
"Mathematics",
"English"
};
I want to filter my LINQ query in order to return only data related to one or many main Domain:
expression = i => _mainDomains.Any(s => i.Domains.Contains(s));
var results = (from v_lq in context.my_view
select v_lq).Where(expression)
The problem is I can't use the Any key word, nor the Exists keyword, since they aren't available in SQL. I've seen many solutions using the Contains keyword, but it doesn't fit to my problem.
What should I do?
You can use contains:
where i.Domains.Any(s => _mainDomains.Contains<string>(s.xxx))
Notice that the generic arguments are required (even if Resharper might tell you they are not). They are required to select Enumerable.Contains, not List.Contains. The latter one is not translatable (which I consider an error in the L2S product design).
(I might not have gotten the query exactly right for your data model. Please just adapt it to your needs).
I figured it out. Since I can't use the Any keyword, I used this function:
public static bool ContainsAny(this string databaseString, List<string> stringList)
{
if (databaseString == null)
{
return false;
}
foreach (string s in stringList)
{
if (databaseString.Contains(s))
{
return true;
}
}
return false;
}
So then I can use this expression in my Where clause:
expression = i => i.Domains.ContainsAny(_mainDomains);
Update:
According to usr, the query would return all the values and execute the where clause server side. A better solution would be to use a different approach (and not use stuffed/comma-separated values)

Genericising one class to handle multiple types

I have a series of about 30 lookup tables in my database schema, all with the same layout (and I would prefer to keep them as separate tables rather than one lookup table), and thus my Linq2SQL context has 30 entities for these lookup tables.
I have a standard class that I would use for CRUD operations on each of these 30 entites, for example:
public class ExampleAttributes : IAttributeList
{
#region IAttributeList Members
public bool AddItem(string Item, int SortOrder)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = new ExampleAttribute();
a.Name = Item;
a.SortOrder = SortOrder;
context.ExampleAttributes.InsertOnSubmit(a);
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public bool DeleteItem(int Id)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = (from m in context.ExampleAttributes
where m.Id == Id
select m).FirstOrDefault();
if (a == null)
return true;
// Make sure nothing is using it
int Count = (from m in context.Businesses
where m.ExampleAttributeId == a.Id
select m).Count();
if (Count > 0)
return false;
// Delete the item
context.ExampleAttributes.DeleteOnSubmit(a);
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public bool UpdateItem(int Id, string Item, int SortOrder)
{
MyDataContext context = ContextHelper.GetContext();
ExampleAttribute a = (from m in context.ExampleAttributes
where m.Id == Id
select m).FirstOrDefault();
a.Name = Item;
a.SortOrder = SortOrder;
try
{
context.SubmitChanges();
return true;
}
catch
{
return false;
}
}
public String GetItem(int Id)
{
MyDataContext context = ContextHelper.GetContext();
var Attribute = (from a in context.ExampleAttributes
where a.Id == Id
select a).FirstOrDefault();
return Attribute.Name;
}
public Dictionary<int, string> GetItems()
{
Dictionary<int, string> Attributes = new Dictionary<int, string>();
MyDataContext context = ContextHelper.GetContext();
context.ObjectTrackingEnabled = false;
Attributes = (from o in context.ExampleAttributes orderby o.Name select new { o.Id, o.Name }).AsEnumerable().ToDictionary(k => k.Id, v => v.Name);
return Attributes;
}
#endregion
}
I could replicate this class 30 times with very minor changes for each lookup entity, but that seems messy somehow - so can this class be genericised so I can also pass it the type I want, and have it handle internally the type differences in the linq queries? That way, I have one class to make additions to, one class to bug fix et al - seems the way that it should be done.
UPDATE:
Andrews answer below gave me the option that I was really looking at while thinking about the question (passing the type in) but I need more clarification on how to genericise the linq queries. Can anyone clarify this?
Cheers
Moo
There are a couple things you can try.
One is to define an interface that has all the relevant fields that the thirty entity classes share. Then, you would be able to have each entity class implement this interface (let's call it IMyEntity) by doing something like
public partial class EntityNumber1 : IMyEntity
{
}
for each entity (where EntityNumber1 is the name of one of the entity classes). Granted, this is still thirty different definitions, but your CRUD operation class could then operate on IMyEntity instead of having to write a new class each time.
A second way to do this is simply to genericize the CRUD operation class, as you suggest:
public class ExampleAttributes<T> : IAttributeList
{
...
which allows you to use T as the type on which to operate. Granted, this might be easier in combination with the first method, since you would still have to check for the presence of the attributes and cast the entity to the appropriate type or interface.
Edit:
To check for the presence of the appropriate properties on the entity, you might need to use reflection methods. One way to check whether the given type T has a particular property might be to check for
typeof(T).GetProperties().OfType<PropertyInfo>().Count<PropertyInfo>(pi => pi.Name == "MyPropertyName" && pi.GetGetMethod().ReturnType == typeof(TypeIWant)) > 0
Of course, replace TypeIWant with the type you are expecting the property to be, and replace MyPropertyName with the name of the property for which you are checking.
Add a parameter to the constructors which specifies the type. Then you can work with it internally. One class, with perhaps a switch statement in the constructor.
For genericising a LINQ query, the biggest problem is that your DataContext has the collections based on type. There are a few ways this can be circumvented. You could try to access it using reflection, but that will require quite a bit of hacking and would pretty much destroy all efficiency that LINQ to SQL would provide.
The easiest way seems to be to use Dynamic LINQ. I have not used it personally, but it seems like it should support it. You can find more information in this thread: Generic LINQ query predicate?
and on http://aspalliance.com/1569_Dynamic_LINQ_Part_1_Using_the_LINQ_Dynamic_Query_Library.1
Maybe someone else can provide more information about this?
This isn't necessarily an answer to the question, but may be a solution to your problem. Have you considered generating all the classes that you need? T4 is built into Visual Studio, and can generate code for you. The link below describes it fairly broadly, but contains heaps of links for further information.
http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx
That way, you can define all the methods in one place, and generate the class files for your 30-odd lookup models. One place to make changes etc.
Maybe worth considering, and if not, still worth knowing about.

Categories