Prevent duplicates when inserting - c#

I have some tags which I need to insert into the Tag database. The Tag database has only one column 'tag' which is also the primary key. This was the trick to prevent duplicates while inserting.
So now the code and the problem.
foreach (string tagval in tagarray)
{
try
{
var tag = new Tag
{
Tag1 = tagval
};
db.AddToTags(tag);
}
catch
{
}
}
db.SaveChanges();
The problem with this approach is after calling SaveChanges() if a duplicate is found early, the program exists without saving the other tags. If I call SaveChanges() after every addition to the table, the program will become inefficient and a lot of calls would need to be made. How to continue insertion even after the earlier insertions fail?
An alternate solution is also welcomed.

Couple of things you need to change here. First off, you're best off removing the duplicates from your own list before going anywhere near the database by calling .Distinct on your list to insert.
Also, there is no need for the try catch here, you should just check what's already in your database before your do the insert. Try this:
List<string> uniqueItems = tagarray
.Distinct()
.Where(x => !db.Tags.Contains(x))
.ToList();
foreach (string uniqueItem in uniqueItems)
{
var tag = new Tag
{
Tag1 = tagval
};
db.AddToTags(tag);
}
db.SaveChanges();

With Entity Framework and an ObjectContext derivation you could do somthing like this.
foreach (var newTag in tagarray.Select(t =>
new Tag { Tag1 = t }).Except(db.Tags))
{
db.Tags.AddObject(newTag);
}
try
{
db.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
}
catch (OptimisitcConcurrencyException)
{
db.Refresh(RefreshMode.StoreWins, db.Tags);
foreach (var newTag in tagarray.Select(t =>
new Tag { Tag1 = t }).Except(db.Tags))
{
db.Tags.AddObject(newTag);
}
db.SaveChanges();
}

foreach (string tagval in tagarray)
{
try
{
var tag = new Tag
{
Tag1 = tagval
};
if(Tags.Where(e =>tag ) != null)
{
dataContext.AddToTags(tag);
}
}
catch
{
}
}
dataContext.SaveChanges();

Related

Algorithm to implement a JOIN with some limitation

Let me explain my matching problem with a real example (the problem is generic). Assume having 2 lists: of "selections" loaded from different sources. The list don't have duplicates.
Let's say mkTPL.Selections and mkDB.Selections come from SQL Tables each with an unique index on the id and the selection's name. The problem is that sometimes IdSelectionType is null (in the selection from mkTPL.Selections)
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}
public bool IsStessaSelezione(SelectionPrints selDb)
{
if (selDb.IdSelectionType == this.IdSelectionType)
return true;
else
{
bool isSameName = selDb.Name == this.Name;
bool isSimilarName = false;
if (!isSameName)
{
isSimilarName = RegexReplace(selDb.Name, #"\([\d.]+\)") == RegexReplace(this.Name, #"\([\d.]+\)");
}
return isSameName || isSimilarName;
}
}
The match alghtoritm that I have implemented is not efficient. Once a selection is matched I shouldn't try to match it further with others (because of the unique index on the id and on the selection name).
Linq could provide me an easy solution?
First of all, you should break when you found a match:
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
break; // <--
}
}
}
Second, I would make a dictionary of mkDB.Selections, where you store the regexed value so you don't have to make that calculation over and over again, on every iteration.
Something like:
var mkDBDictionary = mkDB.Selections.ToDictionary(s => RegexReplace(s.Name, #"\([\d.]+\)"), s => s);
foreach (var selTPL in mkTPL.Selections)
{
string selTPLName = RegexReplace(selTPL.Name, #"\([\d.]+\)");
if (mkDBDictionary.TryGetValue(selTPLName, out var selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}

The property 'ID" is part of the object's key on INSERT

We have to transfer data from one database to another. So I tried to write a program, which reads tables from the old database, create Entities and store them afterwards in the new database. At the beginning it worked very good. I tried to read only one table and transfer it to the new one. Now i receive the following error:
"The property 'Id' is part of the object's key information and cannot
be modified.
No I dont get rid of that error. Even if I try to get back to the first implementation (which worked like a charm).Here I have the definition of the Table:
Table definition
And here the code:
class MappingUtility
{
public static IEnumerable<Nation> MapNation(DataTable table, IModelFactoryService service)
{
IEnumerable<DataRow> rows = table.AsEnumerable();
Nation nat = service.Create<Nation>();
foreach(var nation in rows)
{
nat.Id = (System.Guid)nation.ItemArray[0];
nat.HVCode = (string)nation.ItemArray[1];
nat.Kurzbezeichung = (string)nation.ItemArray[2];
nat.KFZ = (string)nation.ItemArray[3];
nat.iso_a2 = (string)nation.ItemArray[4];
nat.iso_a3 = (string)nation.ItemArray[5];
nat.iso_n3 = (string)nation.ItemArray[6];
nat.Vollbezeichung = (string)nation.ItemArray[7];
nat.Updated = DateTime.Now;
nat.Created = DateTime.Now;
yield return nat;
}
}
}
using (var da = new SqlDataAdapter("SELECT * FROM NATION", "....."))
{
var table = new DataTable();
da.Fill(table);
using (var context = new DAtenbankContext())
{
int i = 0;
foreach (var nation in MappingUtility.MapNation(table, ef6))
{
Debug.WriteLine(i++);
if (context.Nation.Where(p => p.Id == nation.Id).FirstOrDefault() == null)
{
try
{
context.Entry(nation).State = EntityState.Added;
context.SaveChanges();
}
catch(DbEntityValidationException ex)
{
Debug.WriteLine("");
}
catch (DbUpdateException ex)
{
Debug.WriteLine("There where some duplicate columns in the old table.");
Debug.WriteLine(ex.StackTrace);
}
}
}
}
}
Note: The id is not autogenerated. If I try to create only one Nation at a time i can insert it. Even with this for loop I insert one nation, at the second iteration I get the error.
I suspect that you're operating on the same instance of Nation with every iteration of the loop. It appears that you only ever create one instance and then modify it over time. Entity Framework is trying to track that instance, so modifying the key is confusing it.
Move the instantiation into the loop so that you're creating new instances:
IEnumerable<DataRow> rows = table.AsEnumerable();
foreach(var nation in rows)
{
Nation nat = service.Create<Nation>();
// ...
yield return nat;
}

Deleting and Adding child entities in same transaction confusing nhibernate

I'm getting the classic error:
'deleted object would be re-saved by cascade
(remove deleted object from associations)[DrillingContracts.Domain.TrackedField#3216'
But with an added twist, The error is originating entirely because I'm deleting one entity and adding a NEW one.
I empty all the pre-existing children with this method
public void RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in parentNode.ChildNodes)
{
_trackedFieldRepository.Delete(trackedField);
parentNode.RemoveChildNode(trackedField);
}
}
Then add the new ones immediately next
public virtual void AddTrackedChildFieldsToTrackedCell(
params TrackedField[] nodes)
{
foreach (var field in nodes)
{
if (IsPath(field.Name))
{
throw new InvalidTrackedFieldNameException(
"The value " + field.Name + " is not a valid tracked field name.");
}
field.Supplement = this;
_trackedFields.Add(field);
}
}
For those of you who want to know what is happening in the repository the best answer I have is magic. 10,000% magic. The original developer used NCommon. Both of these methods are called from a method wrapped in the NCommon.UnitOfWork attribute.
It should be noted that each method performs as expected on its own. (IE, no fields to delete the add works, and likewise, no fields to add the delete works.)
EDIT
[HttpPost]
[UnitOfWork(Scope = FilterScope.Result)]
public ActionResult SaveEditMode(long id, AddTrackedRowViewModel model, string editMode, List<string> elementNames, string provisionData)
{
var supplement = _supplementCoordinator.GetSupplement(id);
var table = supplement.TrackedTables.First(x => x.Name == model.Name);
var valueAttributes = JsonSerializer.DeserializeFromString<List<ValueAttributeViewModel>>(provisionData);
foreach (var prop in valueAttributes)
{
supplement.Set(prop.Attribute, prop.Value, false);
}
var cell = table.TrackedRows.First(x => x.Ordinal == model.Ordinal).TrackedCells.First(x => x.Name == "Detail");
_supplementCoordinator.RemoveChildren(cell);
if (elementNames != null)
{
var childNodes = elementNames.Select((t, i) => new TrackedField(cell, t, i)).ToList();
supplement.AddTrackedChildFieldsToTrackedCell(childNodes.ToArray());
}
return SwitchEditMode(model, editMode, table);
}
Answer
public void AddChildren(Supplement supplement, TrackedNode parentNode, params TrackedField[] nodes)
{
foreach (TrackedField trackedField in nodes)
{
parentNode.AddChildNode(trackedField);
}
supplement.AddTrackedChildFieldsToTrackedCell();
}
I wasn't getting them added as new entitles to the parent node, just the associated supplement.
Just for completeness. The message 'deleted object would be re-saved..' was caused by the explicit call session.Delete(instance).
In this case, we only need to 1) remove such item from the old parent collection and 2) append it to new parent. The cascade mapping and session.Flush() will properly upate DB.
Final note: such a "movement" must be done inside one session/transaction, to avoid other issues (e.g 'Row was updated or deleted by another transaction...').

I'm receiving "Collection was modified; enumeration operation may not execute." during foreach

I need confirmation of my approach for this, I'm using EF and ASP.NET MVC and I'm trying to remove entities based on user selection (i.e based on what they have checked/unchecked).
To do this I'm looking at the Ids that are passed from the form from the checkboxes, matching what I have in the database and then first adding any which are new and then removing any which don't match.
Following is the code that I originally had:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection, VMinstanceRole vmodel)
{
try
{
var instancerole = db.instanceRoles.Find(id);
if (ModelState.IsValid)
{
UpdateModel<instanceRole>(instancerole, "instanceRole");
var keys = instancerole.rights.Select( c => c.Id);
foreach (var pid in vmodel.selectedId.Except(keys))
{
var right = new right { Id = pid };
db.rights.Attach(right);
instancerole.rights.Add(right);
}
foreach (var pid in keys.Except(vmodel.selectedId))
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
instancerole.rights.Remove(right);
}
db.SaveChanges();
}
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch (InvalidCastException e)
{
return View();
}
}
However, the following error was presented "Collection was modified; enumeration operation may not execute."
So to try and resolve this I decided to keep a seperate list and remove it based on teh list afterwards to overcome the error:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection, VMinstanceRole vmodel)
{
try
{
var instancerole = db.instanceRoles.Find(id);
List<right> removeList = new List<right>();
if (ModelState.IsValid)
{
UpdateModel<instanceRole>(instancerole, "instanceRole");
var keys = instancerole.rights.Select( c => c.Id);
foreach (var pid in vmodel.selectedId.Except(keys))
{
var right = new right { Id = pid };
db.rights.Attach(right);
instancerole.rights.Add(right);
}
foreach (var pid in keys.Except(vmodel.selectedId))
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
removeList.Add(right);
}
foreach (var right in removeList)
{
instancerole.rights.Remove(right);
}
db.SaveChanges();
}
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch (InvalidCastException e)
{
return View();
}
}
This seems to work, however, I'm not sure whether I've done the right thing. Mainly becuase I'm doing another loop. Is there a better way to approach this or is this good enough ?
You found one standard solution. The other solution that works would be to call ToList on the LINQ operation that produces your keys object: doing so would disconnect keys from instanceroles collection, allowing for arbitrary independent modifications on the original collection.
Try this:
foreach (var pid in keys.Except(vmodel.selectedId).ToList())
{
var right = instancerole.rights.Where(c => c.Id == pid).Single();
instancerole.rights.Remove(right);
}
Enumerator you enumerate in foreach loop will be already disposed by the moment you delete your first item.
The reason for not being able to edit a collection when enumerating with foreach is well enough documented here alone (just check the 'related' links to the side), and in knowing you can't do that, you could use a simple for loop and amend to index upon removal of an item - this allows you to maintain one loop.
for (int i = 0; i < max; i++) {
//if removing an item
//manipulate the index as desired...
i--;
}

Lucene.NET and searching on multiple fields with specific values

I've created an index with various bits of data for each document I've added, each document can differ in it field name.
Later on, when I come to search the index I need to query it with exact field/ values - for example:
FieldName1 = X AND FieldName2 = Y AND FieldName3 = Z
What's the best way of constructing the following using Lucene .NET:
What analyser is best to use for this exact match type?
Upon retrieving a match, I only need one specific field to be returned (which I add to each document) - should this be the only one stored?
Later on I'll need to support keyword searching (so a field can have a list of values and I'll need to do a partial match).
The fields and values come from a Dictionary<string, string>. It's not user input, it's constructed from code.
Thanks,
Kieron
Well, I figured it out eventually - here's my take on it (this could be completely wrong, but it works for):
public Guid? Find (Dictionary<string, string> searchTerms)
{
if (searchTerms == null)
throw new ArgumentNullException ("searchTerms");
try
{
var directory = FSDirectory.Open (new DirectoryInfo (IndexRoot));
if (!IndexReader.IndexExists (directory))
return null;
var mainQuery = new BooleanQuery ();
foreach (var pair in searchTerms)
{
var parser = new QueryParser (
Lucene.Net.Util.Version.LUCENE_CURRENT, pair.Key, GetAnalyzer ());
var query = parser.Parse (pair.Value);
mainQuery.Add (query, BooleanClause.Occur.MUST);
}
var searcher = new IndexSearcher (directory, true);
try
{
var results = searcher.Search (mainQuery, (Filter)null, 10);
if (results.totalHits != 1)
return null;
return Guid.Parse (searcher.Doc (results.scoreDocs[0].doc).Get (ContentIdKey));
}
catch
{
throw;
}
finally
{
if (searcher != null)
searcher.Close ();
}
}
catch
{
throw;
}
}

Categories

Resources