I am writing some code which maps LDAP property names to friendly names and back. There are just simple classes called DirectoryProperty:
public class DirectoryProperty
{
public string Id { get; set; }
public string Name { get; set; }
public string HelpText {get; set; }
public DirectoryProperty(string id, string name)
{
Id = id;
Name = name;
}
}
I then have code using a HashSet to build up a collection of these objects. I've got a fiexed set of properties that I supply but I want to allow others to add their own items. A set seems like a good structure for this because when you query LDAP you don't want to have repeating properties, and this also applies to UI where users select from a list of properties.
public class PropertyMapper
{
readonly HashSet<DirectoryProperty> props = new HashSet<DirectoryProperty>(new DirectoryPropertyComparer());
public PropertyMapper() // Will eventually pass data in here
{
props.Add(new DirectoryProperty("displayName", "Display Name"));
props.Add(new DirectoryProperty("displayName", "Display Name")); //err
props.Add(new DirectoryProperty("xyz", "Profile Path")); //err
props.Add(new DirectoryProperty("samAccountName", "User Account Name"));
props.Add(new DirectoryProperty("mobile", "Mobile Number"));
props.Add(new DirectoryProperty("profilePath", "Profile Path"));
}
public List<string> GetProperties()
{
return props.Select(directoryProperty => directoryProperty.Id).OrderBy(p => p).ToList();
}
public List<string> GetFriendlyNames()
{
return props.Select(directoryProperty => directoryProperty.Name).OrderBy(p => p).ToList();
}
}
As you can see I've got 2 problem data items in the constructor right now. The first of these is an obvious duplicate, and the other is a duplicate based on the Name property of DirectoryProperty.
My initial implementation of IEqualityComparer looks like:
class DirectoryPropertyComparer : IEqualityComparer<DirectoryProperty>
{
public bool Equals(DirectoryProperty x, DirectoryProperty y)
{
if (x.Id.ToLower() == y.Id.ToLower() || x.Name.ToLower() == y.Name.ToLower())
{
return true;
}
return false;
}
public int GetHashCode(DirectoryProperty obj)
{
return (obj.Id.Length ^ obj.Name.Length).GetHashCode();
}
}
Is there anything I can do to ensure that the Id, and Name properties of DirectoryProperty are both checked for uniqueness to ensure that duplicates based on either are caught? I'm probably being too strict here and I live with my existing code because it seems like it handles duplicate Id's OK but I'm interested in learning more about this.
It´s unclear exactly what you´re trying to do:
Your equality method considers two values equal if either their names or their IDs are the same
Your GetHashCode method includes both values, so (accidental collisions aside) you´re only matching if both the name and the ID have the same lengths in two objects
Fundamentaly, the first approach is flawed. Consider three entries:
A = { Name=¨N1¨, ID=¨ID1¨ }
B = { Name=¨N2¨, ID=¨ID1¨ }
C = { Name=¨N2¨, ID=¨ID2¨ }
You appear to want:
A.Equals(B) - true
B.Equals(C) - true
A.Equals(C) - false
That violates the rules of Equality (transitivity).
I strongly suggest you simply have two sets - one comparing values by ID, the other comparing values by Name. Then write a method to only add an entry to both sets if it doesn´t occur in either of them.
This approach is not going to work. The Equals method needs to define an equivalence relationship, and such a relationship cannot be defined in this manner.
An equivalence relationship must be transitive, but this relation is not transitive.
{"A", "B"} == {"C", "B"}
and
{"A", "B"} == {"A", "D"}
but
{"C", "B"} != {"A", "D"}
A better approach would be to create two dictionaries—one for ID and one for Name—and check both dictionaries for collisions before adding a new value.
Related
I have an entity Position that has list of Good
Position
{
public int id { get; set; }
//other properties
public virtual ICollection<Good> Good { get; set; }
}
Good
{
public int id { get; set; }
public string name { get; set; }
public int positionId { get; set; }
public virtual Position Position { get; set; }
}
So if I have:
Position 1
Good 1, name = "A"
Good 2, name = "A"
Good 3, name = "B"
Position 2
Good 1, name = "A"
Good 2, name = "D"
Position 3
Good 1, name = "C"
Good 2, name = "D"
And search for Good with name = "A", it must return
Position 1
Good 1, name = "A"
Good 2, name = "A"
Position 2
Good 1, name = "A"
In other words - Position entities with List that contains only filtered Good.
How can I achieve this with minimum trips to database and minimum records loaded? Any tips are welcome
Well done! I see you use the proper Entity Framework method to model a one-to-many relation between Positions and Good. Every Position has zero or more Goods, and every Good belongs to exactly one Position.
The only advise I would give is to be consistent in your capitalization (PositionId instead of positionId) and use the plurals correctly: a Position has zero or more Goods (not: a Position has zero of more Good). Consistent usage of this makes reading your queries easier, which enhances maintainability. Besides usage of proper capitalization and pluralization minimizes the need for Attributes and Fluent API.
So you want the sequence of all Positions that have at least one Good with Name equals "A", together with all its Goods that have this name.
I experience that once I've modeled the one-to-many relations correctly in entity framework I seldom use a join anymore. I think in collections that have zero or more items from other collections (Positions that have Goods), or Collections that are part of other elements (Goods that belong to a position). Entity Framework will translate this into a join.
Your query would be:
var result = myDbContext.Positions.Select(position => new
{
... // use the Position properties you want in your result for example:
Id = position.Id,
// from the collection of goods, take only the goods with name equals "A":
Goods = position.Goods.Where(good => good.Name == "A"),
})
// keep only those items that have at least one Good
.Where(element => element.Goods.Any());
In words: From every position in the collection of Positions make one new (anonymous type) object with several properties:
The properties you want from the Position. In your example this was something like "Position1"
The sequence of all Goods from this Position that have a Name "A"
From the resulting collection keep only those elements that have at least one Good.
Entity framework will know that this is done using an SQL join / where / select
Addition after comment: query without anonymous objects
In the example above I created anonymous classes, because that is the easiest and most efficient if you don't want the complete objects. SQL will only do a select on the properties you request.
It might be that anonymous objects are not good enough for you. For instance you want to pass the result of the query to other functions, or you want the result in an object that has Methods and other Properties. In that case you can create a special class that contains the result.
Note that SQL does not know your special classes, so you can only use classes without constructor.
The Select in the query will slightly differ:
class SpecialPosition
{
public int Id {set; set;}
public string Name {get; set}
public ICollection<SpecialGood> Goods {get; set;
}
class SpecialGood
{
public int Id {set; set;}
public string Name {get; set}
}
IEnumerable<SpecialPosition> result = myDbContext.Positions
.Select(position => new SpecialPosition()
{
Id = position.Id,
Name = position.Name,
Goods = position.Goods
.Select(good => new SpecialGood()
{
Id = good.Id,
Name = good.Name,
}
.Where(speicalGood => specialGood.Name == "A"),
})
// keep only those items that have at least one Good
.Where(element => element.Goods.Any());
Try for yourself what happens if you don't create SpecialPositions and SpecialGoods, but Positions and Goods
How about this?
list.Where(p => p.Good.Any(g => g.Name.Equals("A", StringComparison.OrdinalIgnoreCase)))
(where the "A" should be replaced with a parameter)
Without knowing your database structure, this seems to be a problem that can be done with a simple Join: https://www.w3schools.com/sql/sql_join_left.asp
Alternatively you could try to solve the problem with a linq GroupBy:
allPositions.SelectMany(p => p.Good)
.Where(g => g.name == "A")
.GroupBy(g => g.Position)
.ToDictionary(x => x.Key,x => x.ToList());
I want to compare two different lists with one property in common (Name). I have tried things like the .Contains() method, but that does not work because I am dealing with lists of two different objects. I have tried the solutions in questions like:
C# Compare Two Lists of Different Objects
C#, compare two different type of lists
compare different type of list in c#
But these solutions do not work because my lists expect a Property or Animal object and not a "string", this is where I get stuck.
I have two classes:
public class Animal
{
public string Name = string.Empty;
public string XChromosome = string.Empty;
public string YChromosome = string.Empty;
}
public class Properties
{
public string Name = string.Empty;
public string Prop1 = string.Empty;
public string Prop2 = string.Empty;
}
The two lists look like this:
List<Animal> = "name", "xposition", "yposition"
"name1", "xposition1", "yposition1" etc..
List<Properties> = "name", "prop1","prop2"
"name1", "prop3", "prop4" etc..
What I would like to do do is, compare these two lists and if the "Name" matches I would like to get the content of both lists belonging to this name. I also tried using a HashSet or a Dictionary, but this is not what I am looking for.
You can join two lists on Name property and get matches as anonymous object:
from a in animals
join p in properties on a.Name equals p.Name
select new {
a.Name,
a.XChromosome,
a.YChromosome,
p.Prop1,
p.Prop2
}
You can try it yourself in .NET Fiddle.
NOTE: If you want to get animal info no matter if there is match in properties, or you can have more than one match for given animal, then you need to use group join (check this fiddle for details):
from a in animals
join p in properties on a.Name equals p.Name into g
from p in g.DefaultIfEmpty()
select new {
a.Name,
a.XChromosome,
a.YChromosome,
Prop1 = p?.Prop1,
Prop2 = p?.Prop2
}
That wil return each pair of animal - property joined by name. If no matching property found, then Prop1 and Prop2 will have null values by default (though you can provide any default value you want).
Sergey Berezovsky's solution is good, deserves all the upvotes and to be the accepted answer, however, I would like to add an alternative as well. You could create a class called Named, like this:
class Named {
public string Name = string.Empty;
}
and inherit your classes from it, like this:
public class Animal : Named
{
public string XChromosome = string.Empty;
public string YChromosome = string.Empty;
}
public class Properties : Named
{
public string Prop1 = string.Empty;
public string Prop2 = string.Empty;
}
This will enable you to use List<Named> as type which will allow you to do your task easily either with LINQ or with a cycle. The benefit of this approach is that you will not duplicate the same member in both classes and if you are going to have more cases when you need to do something like this or you are going to have more similar members and/or methods, then you will not duplicate your code.
I have 2 enums
enum Categories{
Entertainment=100,
Food=200,
Medical=300
};
enum Types
{
Multiplex = 101,
GameZone,
Bar = 201,
Hotel,
Cafe,
Hospital = 301,
Clinic
};
I want to list out all types under particular category
eg.
If i give Entertainment as input output list will contain Multiplex and Gamezone
How should I do this?
I would suggest you use a dictionary for you purpose, like below:
Dictionary<Categories,List<Types>> dictionary = new Dictionary<Categories, List<Types>>()
{
{ Categories.Entertainment, new List<Types> { Types.Multiplex , Types.GameZone} },
{ Categories.Food, new List<Type> { Types.Bar, Types.Hotel, Types.Cafe }}
};
This way you could retrive the corresponding list, giving the right key, like below:
dictionary[Categories.Entertainment]
would return the list with elements
Types.Multiplex and Types.GameZone.
First of all, this looks like a really bad design. You have implicit relationships, and not explicit ones (ie. a dictionary or similar type of explicitly denoting what goes with what).
I would seriously consider finding a different way to organise these things.
However, be that as it may, here's one way to obtain the types:
var cat = Categories.Entertainment;
var types =
from Types type in Enum.GetValues(typeof(Types))
where (int)type >= (int)cat && (int)type < (int)cat+100
select type;
I would suggest decorating the type enum values with a custom attribute that pointed it back to the category.
I understand that you have put in range values but my preference would be custom attributes.
Example
With the attribute
public class CategoryAttribute : Attribute
{
private readonly Category _category;
public Category Category
{
get
{
return _category;
}
}
public CategoryAttribute(Category category)
{
_category = category;
}
}
You could have some methods similar to
public static Category GetCateogryFromType(Types categoryType)
{
var memberInfo = typeof(Types).GetMember(categoryType.ToString())
.FirstOrDefault();
if (memberInfo != null)
{
var attribute = (CategoryAttribute)memberInfo.GetCustomAttributes(typeof(CategoryAttribute), false).FirstOrDefault();
if (attribute != null)
{
return attribute.Category;
}
}
throw new ArgumentException("No category found");
}
public static IEnumerable<Types> GetCategoryTypes(Category category)
{
var values = Enum.GetValues(typeof(Types)).Cast<Types>();
return values.Where(t => GetCateogryFromType(t) == category);
}
And then decorate your types like this
public enum Types
{
[Category(Category.Entertainment)]
Multiplex,
[Category(Category.Entertainment)]
GameZone,
[Category(Category.Food)]
Bar,
[Category(Category.Food)]
Hotel,
[Category(Category.Food)]
Cafe,
[Category(Category.Medical)]
Hospital,
[Category(Category.Medical)]
Clinic
}
Then you could call
GetCategoryTypes(Category.Entertainment).ToList();
Oh and I renamed your Categories enum to Category :-p
I have two employee lists that I want to get only unique records from but this has a twist to it. Each list has an Employee class in it:
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID{ get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
The only properties I want to compare on for a match are FirstName and LastName. I want to completely ignore ID in the comparison. The allFulltimeEmployees list has 3 employees in it and the allParttimeEmployees list has 3 employees in it. The first name and last name match on two items in the lists - Sally Jones and Fred Jackson. There is one item in the list that does not match because FirstName is the same, but LastName differs:
emp.id = null; // not populated or used in comparison
emp.FirstName = "Joe"; // same
emp.LastName = "Smith"; // different
allFulltimeEmployees.Add(emp);
emp.id = 3; // not used in comparison
emp.FirstName = "Joe"; // a match
emp.LastName = "Williams"; // not a match - different last name
allParttimeEmployees.Add(emp);
So I want to ignore the ID property in the class during the comparison of the two lists. I want to flag Joe Williams as a non-match since the last names of Smith and Williams in the two lists do not match.
// finalResult should only have Joe Williams in it
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees);
I've tried using an IEqualityComparer but it doesn't work since it is using a single Employee class in the parameters rather than an IEnumerable list:
public class EmployeeEqualityComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
if (x.FirstName == y.FirstName && x.LastName == y.LastName)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
}
How can I successfully do what I want and perform this operation? Thanks for any help!
Your idea of using the IEqualityComparer is fine, it's your execution that is wrong. Notably, your GetHashCode method.
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
IEqualityComparer defines both Equals and GetHashCode because both are important. Do not ignore GetHashCode when you implement this interface! It plays a pivotal role on equality comparisons. No, it is not an indication that two items are equal, but it is an indicator that two elements are not. Two equal elements must return the same hash code. If they do not, they cannot be considered equal. If they do, then they might be equal, and equality functions only then go on to explore Equals.
With your implementation delegating to the GetHashCode method of the actual employee object, you are relying upon the implementation that Employee class uses. Only if that implementation is overriden will it be useful for you, and only if it is using your key fields. And if it is, then it is very likely that you did not need to define your own external comparer in the first place!
Build a GetHashCode method that factors in your key fields and you will be set.
public int GetHashCode(Employee obj)
{
// null handling omitted for brevity, but you will want to
// handle null values appropriately
return obj.FirstName.GetHashCode() * 117
+ obj.LastName.GetHashCode();
}
Once you have this method in place, then use the comparer in your call to Except.
var comparer = new EmployeeEqualityComparer();
var results = allFulltimeEmployees.Except(allParttimeEmployees, comparer);
You can override Equals and GetHashCode in your Employees class.
For example,
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID { get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
var other = obj as Employee;
return this.FirstName == other.FirstName && this.LastName == other.LastName;
}
public override int GetHashCode()
{
return this.FirstName.GetHashCode() ^ this.LastName.GetHashCode();
}
}
I tested with the following data set:
var empList1 = new List<Employee>
{
new Employee{ID = 1, FirstName = "D", LastName = "M"},
new Employee{ID = 2, FirstName = "Foo", LastName = "Bar"}
};
var empList2 = new List<Employee>
{
new Employee { ID = 2, FirstName = "D", LastName = "M" },
new Employee { ID = 1, FirstName = "Foo", LastName = "Baz" }
};
var result = empList1.Except(empList2); // Contained "Foo Bar", ID #2.
your IEqualityComparer should work:
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees, new EmployeeEqualityComparer());
Try implementing the IEquatable(T) interface for your Employee class. You simply need to provide an implementation for an Equals() method, which you can define however you want (i.e. ignoring employee IDs).
The IEquatable interface is used by generic collection objects such
as Dictionary, List, and LinkedList when testing
for equality in such methods as Contains, IndexOf, LastIndexOf, and
Remove. It should be implemented for any object that might be stored
in a generic collection.
Example implementation of the Equals() method:
public bool Equals(Employee other)
{
return (other != null) && (FirstName == other.FirstName) && (LastName == other.LastName);
}
It's not the most elegant solution, but you could make a function like so
public string GetKey(Employee emp)
{
return string.Format("{0}#{1}", emp.FirstName, emp.LastName)
}
and then populate everything in allFullTimeEmployees into a Dictionary<string, Employee> where the key of the dictionary is the result of calling GetKey on each employee object. Then you could loop over allParttimeEmployees and call GetKey on each of those, probing into the dictionary (e.g. using TryGetValue or ContainsKey), and taking whatever action was necessary on a duplicate, such as removing the duplicate from the dictionary.
I have a
ObservableCollection<BasicClass> allCollection;
ObservableCollection<BasicClass> selectedCollection;
where
BasicClass
{
public Name {get;set;}
public Age {get;set;}
}
Now I added many BasicClass items to allCollection and only selected BasicClass to selectedCollection
SomeWhere I want to add items in selectedCollection which are not there in allCollection.
I tried this
foreach(var a in allCollection)
{
foreach(var s in selectedCollection)
if(a.Name!=s.Name)
//selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
But the problem is that this code is adding new BasicClass for each and every unmatched name,
but my actuall requirement is, for each Name of allCollection compare all selectedCollection items. If it is not there then add else move for next Item.
LINQ solution could help this? Actually I achieved this by more if and flags but That looks ver hectic.
My traditional solution
foreach(var a in allCollection)
{
bool same = false;
foreach(var s in selectedCollection)
if(a.Name==s.Name)
same=true;
}
if(same==false)
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
And I hate this..
EDIT:
I don't want compare collection to collection.
I want to compare collection1 value to collection2 all values, and if it not there then I want to add
Are you sure you don't just need this?
foreach(var a in allCollection)
{
if (!selectedCollection.Contains(a))
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
EDIT
I've just seen your comment below about matching on name only, so the above is not really what you want:). Try this approach instead:
foreach(var a in allCollection)
{
if (!selectedCollection.Any(s => a.Name == s.Name))
{
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
}
EDIT
As Chris suggested you could also use "Except" to create a collection. I'm not sure this gains much, it may be quicker but it involves writing the comparer code and creates a new temporary collection. However, it is pretty succinct E.g. Once you had the comparaer written you would just need this to add your missing items to the collection:
selectedCollection.Concat(allCollection.Except(selectedCollection));
So basically you need a 'where-not-in'? Linq->Except is the way to go, to filter on BasicClass.name only implement the IEqualityComparer for Except.
I'm not sure I understood your requirements correctly, so i may be missing the point...
Your BasicClass should implement the IEquatable<BasicClass> interface, so that two instances of BasicClass can be compared for equality:
class BasicClass : IEquatable<BasicClass>
{
public Name {get;set;}
public Age {get;set;}
public bool Equals(BasicClass other)
{
if (other == null)
return false;
return string.Equals(this.Name, other.Name);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
Now you can use the Except method to find items that are in allCollection but not in selectedCollection:
BasicClass[] notSelected = allCollection.Except(selectedCollection).ToArray();
foreach(BasicClass item in notSelected)
{
selectedCollection.Add(item);
}
Alternatively, you can implement a IEqualityComparer<BasicClass> and pass it to Except (instead of implementing IEquatable<BasicClass> in BasicClass)
You're right, this is more easily accomplished with Linq:
var itemsToAdd = allCollection.Except(selectedCollection);
foreach (var item in itemsToAdd)
selectedCollection.Add(item);
On the other hand, this is just going to make both lists contain the exact same items. Sure this is what you want?
If BasicItem overrides 'Equals' and 'GetHashCode' based off of Name, then this is all you need. If it doesn't, then you will also need to implement an IEqualityComparer:
//Allows us to compare BasicItems as if Name is the key
class NameComparer: IEqualityComparer<BasicItem>
{
public bool Equals(BasicItem first, BasicItem second)
{
return first.Name == second.Name;
}
public int GetHashCode(BasicItem value)
{
return value.Name.GetHashCode;
}
}
You now pass an instance of this class to Except:
var itemsToAdd = allCollections.Except(selectedCollection, new NameComparer());