Best Comparison Algorithm using Entity Framework - c#

I was wondering what was the best approach to compare multiple objects that are created and having the state of the objects changed to Inactive (Deleted), while creating history and dependencies.
This also means im comparing past and present objects inside a relational table (MarketCookies).
Id | CookieID | MarketID
The ugly solution i found was calculating how many objects had i changed.
For this purpose lets call the items of the Past: ListP
And the new items: ListF
I divided this method into three steps:
1 - Count both lists;
2 - Find the objects of ListP that are not present in List F and change their state to Inactive and update them;
3 - Create the new Objects and save them.
But this code is very difficult to maintain.. How can i make an easy code to maintain and keep the functionality?
Market Modal:
public class Market()
{
public ICollection<Cookie> Cookies {get; set;}
}
Cookie Modal:
public class Cookie()
{
public int Id {get;set;}
//Foreign Key
public int CookieID {get;set}
//Foreign Key
public int MarketID {get;set;}
}
Code:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
int ListPCount = ListP.Count();
int ListFCount = ListF.Count();
if(ListPCount > ListFCount)
{
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete the Object
}
});
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Object
}
});
}
else if(ListPCount < ListFCount)
{
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Objects
}
});
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete Objects
}
});
}
else if(ListPCount == ListFCount)
{
ListP.Foreach(x =>
{
var ItemExists = ListF.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Delete Objects
}
});
ListF.Foreach(x =>
{
var ItemExists = ListP.Where(y => y.Id == x.Id).FirstOrDefault();
if(ItemExists == null)
{
//Create Objects
}
});
}
}

Without a good, minimal, complete code example that clearly illustrates the question, it's hard to know for sure what even a good implementation would look like, never mind "the best". But, based on your description, it seems like the LINQ Except() method would actually serve your needs reasonably well. For example:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
foreach (var item in ListP.Except(ListF))
{
// set to inactive
}
foreach (var item in ListF.Except(ListP))
{
// create new object
}
}
This of course assumes that your objects have overridden Equals() and GetHashCode(). If not, you can provide your own implementation of IEqualityComparer<T> for the above. For example:
// General-purpose equality comparer implementation for convenience.
// Rather than declaring a new class for each time you want an
// IEqualityComparer<T>, just pass this class appropriate delegates
// to define the actual implementation desired.
class GeneralEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equals;
private readonly Func<T, int> _getHashCode;
public GeneralEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
_equals = equals;
_getHashCode = getHashCode;
}
public bool Equals(T t1, T t2)
{
return _equals(t1, t2);
}
public int GetHashCode(T t)
{
return _getHashCode(t);
}
}
Used like this:
public void UpdateMarket (Market Market, int Id)
{
var ListP = MarketCookiesRepository.GetAll()
.Where(x => x.MarketID == Id && Market.State != "Inactive").ToList();
var ListF = Market.Cookies.ToList();
IEqualityComparer<Cookie> comparer = new GeneralEqualityComparer<Cookie>(
(t1, t2) => t1.Id == t2.Id, t => t.Id.GetHashCode());
foreach (var item in ListP.Except(ListF, comparer))
{
// set to inactive
}
foreach (var item in ListF.Except(ListP, comparer))
{
// create new object
}
}

Related

Update Record with Composite Key - Entity Framework Core

I am having an issue updating a record with a composite key using EF Core 5 and Net Core 5. I am able to get the record using both parts of the composite key (two ids in my case) but when I save the changes to the database I get the following error:
Database operation expected to affect 1 row(s) but actually affected 24 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
After looking in the database, it looks like entity framework is trying to update all the records in the table with same id (first part of the composite key) instead of updating the record that I got.
Has anyone else seen this behavior? Any ideas of how to get around it or what I might be doing wrong?
Here is a basic version of the table set up
Table1:
Id (PK)
Table2:
Id (PK),
Table1Id (PK, FK)
Scenario where it fails:
Table1:
Id
--
1
2
Table2:
Id, Table1Id
------------
1, 1
2, 1
1, 2
2, 2
If I try to update the first record in Table2 (1, 1), EF would try to update it and the third record (1, 2) and throw the above error.
Code:
// Getting record
var record = await context.Table2s
.FirstOrDefaultAsync(e => e.Id == 1 && e.Table1Id == 1, cancellationToken);
// Saving
await context.SaveChangesAsync(cancellationToken);
// I have also tried specifying which record to update before saving
context.Table2s.Update(record);
Configuration for Table2:
builder.ToTable("Table2");
builder.HasKey(e => new { e.Id, e.Table1Id });
builder.HasOne(e => e.Table1)
.WithMany(m => m.Table2s)
.HasForeignKey(e => e.Table1Id)
.OnDelete(DeleteBehavior.Cascade);
It works in my own projects, I hope it will be useful.
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<WebServisUser>().HasKey(x => new
{
x.Proje, /* Composite Keys */
x.KulAdi /* Composite Keys */
});
mb.Entity<WebServisUser>().Property(e => e.Proje).IsUnicode(false);
mb.Entity<WebServisUser>().Property(e => e.KulAdi).IsUnicode(false);
}
I have added the link other functions for better understanding.
public static class Helper
{
public static bool IsNull(this object value) => (value == null || Convert.IsDBNull(value) || Convert.GetTypeCode(value) == TypeCode.Empty);
public static bool IsNotNull(this object value) => !value.IsNull();
public static string ToPropertyName(this Expression value)
{
if (typeof(LambdaExpression).IsAssignableFrom(value.GetType())) { value = ((LambdaExpression)value).Body; }
if (value is MemberExpression e_mem) { return e_mem.Member.Name; }
else if (value is UnaryExpression e_una) { return ((MemberExpression)e_una.Operand).Member.Name; }
else { throw new InvalidCastException(String.Format("value type can be \"{0}\" or \"{1}\"", nameof(MemberExpression), nameof(UnaryExpression))); }
}
public static object ChangeType(object value, Type type)
{
var c = TryTypeIsNullable(type, out Type _outtype);
return (c && value.IsNull()) ? null : (_outtype.IsEnum ? Enum.ToObject(_outtype, value) : Convert.ChangeType(value, c ? Nullable.GetUnderlyingType(type) : type));
}
public static void SetPropertyValue<T>(this T value, string propertyname, object data) where T : class
{
var p = typeof(T).GetProperty(propertyname);
p.SetValue(value, data.IsNull() ? null : ChangeType(data, p.PropertyType));
}
public static bool TryTypeIsNullable(Type value, out Type outvalue)
{
var t = (value.IsGenericType && value.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
outvalue = (t ? value.GenericTypeArguments[0] : value); return t;
}
public static bool TryTypeIsAttribute<T, Y>(T value, out Y outvalue) where T : ICustomAttributeProvider where Y : Attribute
{
bool ret;
try
{
var v = value.GetType();
var y = typeof(Y);
if (typeof(Assembly).IsAssignableFrom(v) && Attribute.IsDefined((Assembly)((object)value), y)) { ret = true; }
else if (typeof(MemberInfo).IsAssignableFrom(v) && Attribute.IsDefined((MemberInfo)((object)value), y)) { ret = true; }
else if (typeof(Module).IsAssignableFrom(v) && Attribute.IsDefined((Module)((object)value), y)) { ret = true; }
else if (typeof(ParameterInfo).IsAssignableFrom(v) && Attribute.IsDefined((ParameterInfo)((object)value), y)) { ret = true; }
else { ret = false; }
outvalue = ret ? value.GetCustomAttributes(y, false).Cast<Y>().Take(1).FirstOrDefault() : default;
}
catch
{
outvalue = default;
ret = false;
}
return ret;
}
public static void SetCompositeKey<T, TKey>(this DbContext context, T entity, Expression<Func<T, TKey>> compositekey, TKey compositekeyvalue, Action<T> actionislem = null) where T : class, new()
{
var t = typeof(T);
var c = compositekey.ToPropertyName();
var ie_prop = typeof(T).GetProperties().Where(x => x.GetMethod.IsNotNull() && x.SetMethod.IsNotNull() && !x.GetMethod.IsVirtual && !x.SetMethod.IsVirtual && !Helper.TryTypeIsAttribute(x, out NotMappedAttribute _)).Select(x => new
{
name = x.Name,
iskey = x.Name == c,
iscompositekey = Helper.TryTypeIsAttribute(x, out KeyAttribute _outkeyvalue) && _outkeyvalue.IsNotNull() && Helper.TryTypeIsAttribute(x, out DatabaseGeneratedAttribute _outdatagenvalue) && _outdatagenvalue.DatabaseGeneratedOption == DatabaseGeneratedOption.None
});
if (ie_prop.Where(x => x.iscompositekey).Count() < 2) { throw new KeyNotFoundException("minimum of 2 composite keys exception message..."); }
else if (ie_prop.Any(x => x.iscompositekey && x.iskey))
{
var addentity = new T();
var e = context.Entry<T>(entity);
var dbset = context.Set<T>();
dbset.Attach(entity);
foreach (var item in ie_prop.Select(x => new
{
x.name,
x.iskey
})) { addentity.SetPropertyValue(item.name, item.iskey ? compositekeyvalue : e.Property(item.name).OriginalValue); }
dbset.Add(addentity); /* Insert record about new key value */
if (actionislem.IsNotNull()) { actionislem(addentity); } /* If there are other areas of connection, do the operations there with the action method. */
dbset.Remove(entity); /* delete old value */
}
else { throw new Exception("compositekey must be composite key exception message..."); }
}
}
and example
using (var mdl = new mdlBayUniOrtak())
{
using (var c = new TransactionScope())
{
var beforeentity = mdl.WebServisUsers.Where(x => x.Proje == "loremipsum").Take(1).FirstOrDefault();
var e = new Action<WebServisUser>((WebServisUser afterentity) =>
{
/* Other actions if necessary. Other foreign key operations can be done here! */
});
mdl.SetCompositeKey(beforeentity, x => x.KulAdi, "galatasaray", e);
mdl.SaveChanges();
c.Complete();
}
}

c# Linq - Check if composite key exists in another list

I have a list of errors defined as the following:
List<Errors> test1 = new List<Errors>();
public class Errors
{
public int ID {get; set;}
public int Occurrence {get; set;}
//.....
//.....
}
The errors are unique by the combination of the two fields above.
A second list keeps track of whose been assigned to the errors.
List<Tasks> test2 = new List<Tasks>();
public class Tasks
{
public int ID {get; set;}
public int Occurrence {get; set;}
public int EmployeeID {get; set;}
//.....
}
Also made unique by the same two fields. Essentially the tasks are a subset of the errors that have been assigned to someone.
I would like to use a LINQ query (or equivalent) to determine if the composite ID from the List<Errors> exists in List<Tasks>... To be clear it must use both IDS.
I have found the below solution but have not been able to adopt it to a composite key.
`var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t2.Contains(t1)));`
Just need to use and && operator and check both properties instead of one:
var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t1.ID == t2.ID && t1.Occurance == t2.Occurance);
There is a function for that... Except
var test2NotInTest1 = test1.Except(test2);
If you don't have it you will need to create the interface for equal -- something like this:
var test2NotInTest1 = test1.Except(test2, new ErrorsComparer());
class ErrorsComparer : IEqualityComparer<Errors>
{
public bool Equals(Errors x, Errors 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.ID == y.ID && x.Occurrence == y.Occurrence;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Errors e)
{
if (Object.ReferenceEquals(e, null)) return 0;
int hashID = e.ID == null ? 0 : e.ID.GetHashCode();
int hashO = e.Occurrence.GetHashCode();
//Calculate the hash code for the product.
return hashID ^ hashO;
}
}
You were almost there, just add a correct condition to the LINQ expression:
var test2NotInTest1 = listOfErrors.Where(e => !listOfTasks.Any(t => t.ID == e.Id && t.Occurrence == e.Occurrence)).ToList();
For: to determine if the composite ID from the Errors exists in Tasks...
Another approach is to use Enumerable.Join Method
var assignedErrors =
errors.Join(tasks,
error => new { Id = error.Id, Occurrence = error.Occurrence },
task => new { Id = task.Id, Occurrence = task.Occurrence },
(error, task) => error);
For: to determine if the composite ID from the Errors not exists in Tasks..., as in your sample:
var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t2.Contains(t1)));
You can use HashSet to "speed up" search for already assigned errors.
var assignedErrors = tasks.Select(task => (task.Id, task.Occurrence)).ToHashSet();
var notAssignedErrors =
errors.Where(error => assignedErrors.Contains((error.Id, error.Occurrence)) == false)
.ToList();
Or create your own domain specific extension method:
public static IEnumerable<Errors> NotAssignedIn(
this IEnumerable<Errors> errors,
IEnumerable<Tasks> tasks)
{
var assigned = new HashSet<(int Id, int Occurrence)>();
foreach (var task in tasks)
{
assigned.Add((task.Id, task.Occurrence));
}
foreach (var error in errors)
{
if (assigned.Contains((error.Id, error.Occurrence)) == false)
{
yield return error;
}
}
}
Usage:
var notAssignedErrors = errors.NotAssignedIn(tasks);

Updating object Linq'd from collection doesn't update the object in collection

I have two simple classes. One is called UseCounter<T> and is a simple container for other objects while holding some information about its use (how many times it's been already used and if it can be reused once again).
Then there is Dispenser<T>, which holds a collection of UseConter<T> and is used to get the right element from the collection and then update its information.
The trouble I have is in the Dispenser's Get() method. It should return the object with the lowest combination of Count and TotalCount, and then call Increase() method in order to increase count.
However, when I run this code, Dispenser<T> always returns the same element. It's like the Linq'd element wasn't a reference to the object, but its copy, so the Increase() method increases only the local object's properties, but not the one in the collection.
I am at loss here, because I have never encountered such a behaviour.
Here comes the code:
UseCounter:
public class UseCounter<T>
{
public T Element { get; private set; }
public int TotalCount { get; private set; }
public int Count { get; private set; }
public bool Useful { get; set; }
public UseCounter(T element)
{
this.Element = element;
this.Count = 0;
this.TotalCount = 0;
this.Useful = true;
}
public void IncreaseCounter()
{
this.Count++;
this.TotalCount++;
}
public void DecreaseCounter()
{
if(this.Count == 0)
{
throw new ArgumentOutOfRangeException("Count cannot be lower than 0!");
}
this.Count--;
}
}
Dispenser:
private readonly object _elementLocker = new object();
private IEnumerable<UseCounter<T>> _elements;
public IEnumerable<T> Elements
{
get { return _elements.Select(e => e.Element); }
}
public int FinishedCount
{
get
{
lock (_elementLocker)
{
return _elements.Where(e => e.Useful == false).Count();
}
}
}
public int CurrentlyWorkingCount
{
get
{
lock (_elementLocker)
{
return _elements.Where(e => e.Count > 0).Count();
}
}
}
public Dispenser(IEnumerable<T> elements)
{
this._elements = elements
.Distinct()
.Select(e => new UseCounter<T>(e));
}
public T Get()
{
lock(_elementLocker)
{
var lCount = _elements
.Where(e => e.Useful == true)
.Select(e => e.Count).Min();
var lTCount = _elements
.Where(e => e.Useful == true)
.Where(e => e.Count == lCount)
.Select(e => e.TotalCount).Min();
var el = _elements
.Where(e => e.Useful == true)
.First(e => e.Count == lCount && e.TotalCount == lTCount);
el.IncreaseCounter();
return el.Element;
}
}
public void Report(T element, bool successful)
{
lock(_elementLocker)
{
var el = _elements
.First(e => e.Element == element);
el.DecreaseCounter();
if(el.Useful == true && successful == true)
{
el.Useful = false;
}
}
}
This is the problem:
this._elements = elements
.Distinct()
.Select(e => new UseCounter<T>(e));
This is lazy. Every time you iterate over _elements, it will go back to the original source data, find a distinct set, and then project that set onto a new sequence of UseCounter instances. Any changes you make to those instances are irrelevant to what happens when you next iterate over _elements.
If you're happy for the set of input elements to be "frozen" on construction, you can just materialize the query so it only executes once:
this._elements = elements
.Distinct()
.Select(e => new UseCounter<T>(e))
.ToList();
Now every time you iterate over elements, you're iterating over the same sequence of references.

Comparing two classes in LINQ - getting 'mismatches'

I have the following class:
public class DocumentCompare
{
public string Customer;
public string Filename;
public string Reference;
public DateTime? Date;
public override bool Equals(object obj)
{
if (obj == null)
return false;
DocumentCompare doc = obj as DocumentCompare;
if ((Object)doc == null)
return false;
return (doc.Customer == Customer) && (doc.Date == Date) && (doc.Filename == Filename) && (doc.Reference == Reference);
}
public bool Equals(DocumentCompare doc)
{
if ((object)doc == null)
return false;
return (doc.Customer == Customer) && (doc.Date == Date) && (doc.Filename == Filename) && (doc.Reference == Reference);
}
public override int GetHashCode()
{
return string.Format("{0}_{1}_{2}_{3}",Customer,Filename,Reference,(Date == null ? "" : Date.Value.ToString())).GetHashCode();
}
}
I will be retrieving 2 lists of this class - what I want to do is to compare the two, and get ones that don't exist in both. So if an item exists in x list but not in y, I want to perform an action for the items in this list. If an item exists in y list but not in x, I want to do a different action.
How would I do this? Using LINQ I guess!
EDIT: Performance is not much of an issue - this will only be run once
It sounds like you just want Except:
foreach (var newItem in firstList.Except(secondList))
{
...
}
As an aside:
That's not a terribly nice way of generating a hash code - search for other questions here.
Delegate from Equals(object) to Equals(DocumentCompare) to avoid repetitive logic
Mutable types aren't great candidates for equality comparisons (in particular, one you've used a value as a key in a dictionary, if you change the equality-sensitive components you won't be able to find the key again)
Even if you do want it to be mutable, properties are better for encapsulation than public fields
I would either seal the type or check whether the two objects are exactly the same type, as otherwise you could end up with asymmetric equality
here is the code:
var elementsMissingFromFirstList = firstList.Except(secondList).ToList();
var elementsMissingInSecondList = secondList.Except(firstList).ToList();
now you can perform your actions on these missing elements :)
You can use this method to compare objects of two different Lists. exmp: List and List x and y = DocumentCompare,
public static bool EqualsObject<T>(this T t1, T t2) where T : class
{
var p1 = t1.GetType().Fields();
var p2 = t2.GetType().Fields();
for (int j = 0; j < p1.Length; j++)
{
var x = p1[j].GetValue(t1, null);
var y = p2[j].GetValue(t2, null);
if (x == null && y == null)
continue;
if (x != null && y == null)
return false;
if (x == null)
return false;
if (!x.Equals(y))
{
return false;
}
}
return true;
}
This method will show the difference between these two lists.
public static List<T> DifferentObjects<T>(List<T> t, List<T> t2) where T : class
{
var diff = new List<T>();
if (t != null && t2 != null)
{
foreach (T t1 in t)
{
var state = false;
foreach (T t3 in t2.Where(t3 => EqualsObject(t1,t3)))
{
state = true;
}
if (!state)
{
diff.Add(t1);
}
}
}
return diff;
}
you can use code this way
var t = new List<DocumentCompare>();
var t2 = new List<DocumentCompare>();
t.Add(new DocumentCompare{Customer = "x"});
t.Add(new DocumentCompare{Customer = "y"});
t.Add(new DocumentCompare{Customer = "z"});
t2.Add(new DocumentCompare { Customer = "t" });
t2.Add(new DocumentCompare { Customer = "y" });
t2.Add(new DocumentCompare { Customer = "z" });
var list = DifferentObjects(t, t2);
var list2 = DifferentObjects(t2, t);
you used fields (Customer,FileName etc..) in your class, so that GetType().Fields(); is used in EqualsObject method. if you use property , you should use GetType().Properties(); in EqualsObject method.

Set operations using a specific comparison rule

I have two lists:
List<User> collection1 = new List<User>();
List<User> collection2 = new List<User>();
1) I have to get all items common to both of the lists using LINQ. However, the class User has a lot of properties and I just want to compare FirstName and LastName.
2) How can I get the items in collection1 but not in collection2 using the same comparison rule?
Use Enumerable.Intersect for the first question and Enumerable.Except for the second. To wit:
var common = collection1.Intersect(collection2, new UserEqualityComparer());
var difference = collection1.Except(collection2, new UserEqualityComparer());
Here, of course, I am assuming that UserEqualityComparer implements IEqualityComparer<User> like so:
class UserEqualityComparer : IEqualityComparer<User> {
public bool Equals(User x, User y) {
if (Object.ReferenceEquals(x, y)) {
return true;
}
if (x == null || y == null) {
return false;
}
return x.FirstName == y.FirstName && x.LastName == y.LastName;
}
public int GetHashCode(User obj) {
if (obj == null) {
return 0;
}
return 23 * obj.FirstName.GetHashCode() + obj.LastName.GetHashCode();
}
}

Categories

Resources