Entity Framework 6 - Detached entity prevent saving duplicate navigation properties - c#

The method below takes some detached Nodes as an input parameter. The goal is to load any existing Aliases from the database, insert missing Nodes into the database, and if the detached Node's Alias entity is already in the database, simply set it to the one of the database.
However, on SaveChanges(), it seems that the Alias that already exists in the database is inserted yet again. How do I get around this?
internal async Task InsertMissingNodesToDb(INWatchNode[] nodes)
{
if (nodes.Any(x => x == null)) {
Trace.TraceError("Some null element in nodes array in InsertMissingNodesToDb().");
nodes = nodes.Where(x => x != null).ToArray();
}
// De-dup nodes based on ID
nodes = nodes.GroupBy(x => x.Id).Select(y => y.FirstOrDefault()).ToArray();
List<string> aliasNames = new List<string>();
foreach (var node in nodes) {
foreach (var alias in node.Aliases) {
if (!aliasNames.Contains(alias)) {
aliasNames.Add(alias);
}
}
}
using (var dbContext = Application.GetDbContext()) {
dbContext.Aliases.Where(x => aliasNames.Contains(x.Alias)).Load();
foreach (var node in nodes) {
var entityNode = await dbContext.Nodes.FindAsync(node.Id);
if (entityNode == null) {
entityNode = node is NWatchNode ? (NWatchNode)node : new NWatchNode(node);
for (int i = 0; i < entityNode.AliasEntities.Count; i++) {
var currentElement = entityNode.AliasEntities.ElementAt(i);
var loadedAlias = dbContext.Aliases.Local.
FirstOrDefault(x => x.Alias == currentElement.Alias);
if (loadedAlias != null) {
currentElement.Id = loadedAlias.Id;
currentElement = loadedAlias;
dbContext.Entry(loadedAlias).State = EntityState.Unchanged;
}
}
dbContext.Nodes.Add(entityNode);
}
}
await dbContext.SaveChangesAsync();
}
}

I am suspicious of this section of code.
currentElement.Id = loadedAlias.Id;
currentElement = loadedAlias; // this seems pointless
dbContext.Entry(loadedAlias).State = EntityState.Unchanged;
If you want the line in question to do something, I think you need to set entityNode.AliasEntities.ElementAt(i) to loadedAlias -- setting currentElement to loadedAlias is just overriding the local instance variable to point at loadedAlias and as such seems like pointless code -- you haven't informed the entity that one of its intended children should point to this instance with that line. Something like the following might prevent the issue?
for (int i = 0; i < entityNode.AliasEntities.Count; i++) {
var currentElement = entityNode.AliasEntities.ElementAt(i);
var loadedAlias = dbContext.Aliases.Local.
FirstOrDefault(x => x.Alias == currentElement.Alias);
if (loadedAlias != null) {
currentElement.Id = loadedAlias.Id;
entityNode.AliasEntities.ElementAt(i) = loadedAlias;
dbContext.Entry(loadedAlias).State = EntityState.Unchanged;
}
}

Related

Change loop to Linq C#

One of my function in C# required me to filter some value.So, I try it by using a lot of loop in it. It works but doesn't look like effecient.Any idea on how to convert code below to LinQ?
Any help is appreciated.
var object1 = JsonConvert.DeserializeObject<List<string>>(object1json);
foreach (var item1 in table1)
{
if (item1.Code == InputCode)
{
for (int i = 0; i < object1.Count(); i++)
{
tempData temp = new tempData();
foreach (var item2 in item1.List)
{
if (item2.Code == object1[i])
{
temp.Code = item2.Code;
temp.Description = item2.Description;
}
}
if(temp.Code != null || temp.Description != null)
final.Add(temp);
}
}
}
If you want your code to be more efficient, as pointed out in the comments, converting it to Linq isn't really going to help. It's still the same logic, just written in a different way. If you're going for readability, it can be improved with just a few changes:
foreach (var item1 in table1.Where(i => i.Code == InputCode))
{
foreach (var code in object)
{
// This could be SingleOrDefault, I don't know if you have duplicates in the list or not
var item2 = item1.List.LastOrDefault(i => i.Code == code);
if(item2 != null)
{
final.Add(new tempData
{
Code = item2.Code,
Description = item2.Description,
});
}
}
}
If you convert the whole thing to Linq:
var final = table1.Where(i => i.Code == InputCode)
.SelectMany(item1 => object.Select(code => item1.List.LastOrDefault(i => i.Code == code))
.Where(item2 => item2 != null)
.Select(item2 => new tempData
{
Code = item2.Code,
Description = item2.Description,
})
.ToList();
Personally, I prefer the first option, as it's a bit easier to read.
I guess what you post is sample code instead of actual code otherwise it would be better to avoid keyword like object in C#. Anyway,
var final = table1.Where(item1 => item1.Code == InputCode)
.SelectMany(item1 => item1.List)
.Where(item2 => #object.Contains(item2.Code))
.Where(temp => temp.Code != null || temp.Description != null)
.Select(item2 => new tempData()
{
Code = item2.Code,
Description = item2.Description
});

How to find out duplicate Elements in Xelement

I am trying to find out the duplicate Elements in XElement , and make a generic function to remove duplicates .Something like:
public List<Xelement>RemoveDuplicatesFromXml(List<Xelement> xele)
{ // pass the Xelement List in the Argument and get the List back , after deleting the duplicate entries.
return xele;
}
the xml is as follows:
<Execute ID="7300" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7301" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7302" Attrib1="xyz1" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
I want get duplicates on every attribute excluding ID ,and then delete the one having lesser ID.
Thanks,
You can implement custom IEqualityComparer for this task
class XComparer : IEqualityComparer<XElement>
{
public IList<string> _exceptions;
public XComparer(params string[] exceptions)
{
_exceptions = new List<string>(exceptions);
}
public bool Equals(XElement a, XElement b)
{
var attA = a.Attributes().ToList();
var attB = b.Attributes().ToList();
var setA = AttributeNames(attA);
var setB = AttributeNames(attB);
if (!setA.SetEquals(setB))
{
return false;
}
foreach (var e in setA)
{
var xa = attA.First(x => x.Name.LocalName == e);
var xb = attB.First(x => x.Name.LocalName == e);
if (xa.Value == null && xb.Value == null)
continue;
if (xa.Value == null || xb.Value == null)
return false;
if (!xa.Value.Equals(xb.Value))
{
return false;
}
}
return true;
}
private HashSet<string> AttributeNames(IList<XAttribute> e)
{
return new HashSet<string>(e.Select(x =>x.Name.LocalName).Except(_exceptions));
}
public int GetHashCode(XElement e)
{
var h = 0;
var atts = e.Attributes().ToList();
var names = AttributeNames(atts);
foreach (var a in names)
{
var xa = atts.First(x => x.Name.LocalName == a);
if (xa.Value != null)
{
h = h ^ xa.Value.GetHashCode();
}
}
return h;
}
}
Usage:
var comp = new XComparer("ID");
var distXEle = xele.Distinct(comp);
Please note that IEqualityComparer implementation in this answer only compare LocalName and doesn't take namespace into considerataion. If you have element with duplicate local name attribute, then this implementation will take the first one.
You can see the demo here : https://dotnetfiddle.net/w2DteS
Edit
If you want to
delete the one having lesser ID
It means you want the largest ID, then you can chain the .Distinct call with .Select.
var comp = new XComparer("ID");
var distXEle = xele
.Distinct(comp)
.Select(z => xele
.Where(a => comp.Equals(z, a))
.OrderByDescending(a => int.Parse(a.Attribute("ID").Value))
.First()
);
It will guarantee that you get the element with largest ID.
Use Linq GroupBy
var doc = XDocument.Parse(yourXmlString);
var groups = doc.Root
.Elements()
.GroupBy(element => new
{
Attrib1 = element.Attribute("Attrib1").Value,
Attrib2 = element.Attribute("Attrib2").Value,
Attrib3 = element.Attribute("Attrib3").Value,
Attrib4 = element.Attribute("Attrib4").Value,
Attrib5 = element.Attribute("Attrib5").Value
});
var duplicates = group1.SelectMany(group =>
{
if(group.Count() == 1) // remove this if you want only duplicates
{
return group;
}
int minId = group.Min(element => int.Parse(element.Attribute("ID").Value));
return group.Where(element => int.Parse(element.Attribute("ID").Value) > minId);
});
Solution above will remove elements with lesser ID which have duplicates by attributes.
If you want return only elements which have duplicates then remove if fork from last lambda

Remove 'duplicates' from a list of pairings

Title could be misleading, so an example:
I have a class:
class Pair
{
Book Book1;
Book Book2;
}
I have a list of these:
var list = new List<Pair>();
list.Add(new Pair() {
Book1 = new Book() { Id = 123 },
Book2 = new Book() { Id = 456 }
});
list.Add(new Pair() {
Book1 = new Book() { Id = 456 },
Book2 = new Book() { Id = 123 }
});
Now, despite the fact the books are 'flipped', my system should treat these as duplicates.
I need a method to remove one of these 'duplicates' from the list (any one - so let's say the first to make it simple).
What I've Tried
var tempList = new List<Pair>();
tempList.AddRange(pairs);
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
return tempList;
This returns no items (given the example above), as both Pair objects would satisfy the condition in the lambda, I only one to remove one though.
NOTE: This wouldn't happen if I just removed the element from the collection straight away (rather than from a temporary list) - but then I wouldn't be able to iterate over it without exceptions.
You can set up an IEqualityComparer<Pair> concrete class and pass that to the .Distinct() method:
class PairComparer : IEqualityComparer<Pair>
{
public bool Equals(Pair x, Pair y)
{
return (x.Book1.Id == y.Book1.Id && x.Book2.Id == y.Book2.Id)
|| (x.Book1.Id == y.Book2.Id && x.Book2.Id == y.Book1.Id);
}
public int GetHashCode(Pair obj)
{
return obj.Book1.Id.GetHashCode() ^ obj.Book2.Id.GetHashCode();
}
}
And then use it like so:
var distinctPairs = list.Distinct(new PairComparer());
The problem is that you are removing the both duplicates.
Try this:
var uniquePairs = list.ToLookup( p => Tuple.Create(Math.Min(p.Book1.Id, p.Book2.Id), Math.Max(p.Book1.Id, p.Book2.Id)) ).Select( g => g.First() ).ToList();
I would use the following
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id
&& o.Book1.Id > o.Book2.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
This will specifically remove the duplicate that is "out of order". But this (and your original) will fail if the duplicate pairs have the books in the same order.
A better solution (since we're looping over ever pair anyways) would be to use a HashSet
var hashSet = new HashSet<Tuple<int,int>>();
foreach (var item in pairs)
{
var tuple = new Tuple<int,int>();
if (item.Book1.Id < item.Book2.Id)
{
tuple.Item1 = item.Book1.Id;
tuple.Item2 = item.Book2.Id;
}
else
{
tuple.Item1 = item.Book2.Id;
tuple.Item2 = item.Book1.Id;
}
if (hashSet.Contains(tuple))
{
tempList.Remove(dup);
}
else
{
hashSet.Add(tuple);
}
}
I've managed to find a solution, but it's one I'm not happy with. It seems too verbose for the job I'm trying to do. I'm now doing an additional check to see whether a duplicate has already been added to the list:
if(toRemove != null && tempList.Any(o => o.Book1.Id == toRemove.Book2.Id
&& o.Book2.Id == toRemove.Book1.Id))
tempList.Remove(toRemove);
I'm very much open to alternative suggestions.

Converting foreach into single pass Linq Group By

I have a collection of items that may have one or more properties.
I want to use a Linq statement (query syntax) to filter and group that list in a single pass such that it checks for a mandatory property first, and then looks for an optional property.
I can't figure out how to do this without first filtering the list and then going over it again to look for the optional property.
Here's how I could do it in a foreach statement. (Not efficient, just illustrating what I need.)
var usefulBoxes = new Dictionary<Box, int>;
foreach (box in cart)
{
bool boxNeeded = false;
int someCounter = 0;
foreach (prop in box.Properties)
{
if (prop == neededProp)
boxNeeded = true;
else if (boxNeeded && prop == optionalProp)
someCounter += 1;
}
if (boxNeeded)
usefulBoxes.Add(box, someCounter)
}
var usefulBoxes= box.where(b=>b.boxProperties.prop==neededProp).ToList();
this your demo linq:
var usefulBoxes = new Dictionary<List<int>, int>();
foreach (var boxNeeded in from box in cart let boxNeeded = false let someCounter = 0 from prop in box.Properties.Where(prop => prop == neededProp) select boxNeeded)
{
if (prop == neededProp)
boxNeeded = true;
else if (boxNeeded && prop == optionalProp)
someCounter += 1;
if (boxNeeded)
usefulBoxes.Add(box, someCounter);
}

Query by field defined in interface

I have a couple of classes implementing an ISortable interface:
public interface ISortable
{
int Id { get; set; }
int? Idx { get; set; }
}
In my DbContext class I have an update method that should do some extra stuff for entities that implements the ISortable:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id) as ISortable;
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
var entities = FindAll<T>().ToList().Cast<ISortable>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var toUpdate = entities.Where(a => a.Idx >= newIdx && a.Idx < oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
What I'm wondering about is this line:
var entities = FindAll<T>().ToList().Cast<ISortable>();
I have to convert the LINQ to SQL expression to a list in order to cast the entities to ISortable. And I need to cast it to ISortable in order to perform this Where:
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
The Idx attribute is exposed by the interface.
The problem is that calling ToList() on FindAll() loads the whole table into memory.
Is there a way of performing the Where without first loading the whole table, and without losing the generic implementation?
The idea here is that I want to perform some common action on update for all entities that are "sortable". For this to work the update method needs to be generic in order to handle various classes, but then I need the interface to expose the necessary fields... If there's a better way of doing this (there probably is), please let me know. :-)
The problem is that calling ToList() on FindAll() loads the whole table into memory.
Use AsEnumerable instead of ToList; it just changes the compile-time type to IEnumerable<T> instead of IQueryable<T>, so the subsequent operations are executed in memory rather than in the database, but only one item at a time is processed (items are fetched from the DB one by one as needed by the subsequent operations).
Trying again, this time with expressions. I think this should work:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id);
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
UpdateSingleSortable(oldItem, sortable);
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
public void UpdateSingleSortable<T>(T oldItem, ISortable sortable)
where T : class
{
var entities = FindAll<T>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var expression = GenerateExpressionA(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var expression = GenerateExpressionB(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Expression GenerateExpressionB<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx >= newIdx && a.Idx < oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.GreaterThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.LessThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
return toReturn;
}
Expression GenerateExpressionA<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx <= newIdx && a.Idx > oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.LessThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.GreaterThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
toReturn.Dump();
return toReturn;
}
Just change the signature of the method to the following:
public void UpdateSingle<T>(T item)
where T : class, ISortable
Then not only can you perform the query on the DB end (you won't need to pull the collection into memory to get the items meeting the given condition) you also won't be doing the check at runtime; you'll be checking to make sure T implements ISortable at compile time.

Categories

Resources