Entity Framework: Avoiding Inserting Duplicates - c#

Say, I have the following conceptual model, there are strories that have tags (more than one, so it's a many-to-many relationship), plus each tag belongs to a particular category.
My data comes from an external source and before inserting it I want to make sure that no duplicated tags are added.
Updated code snippet:
static void Main(string[] args)
{
Story story1 = new Story();
story1.Title = "Introducing the Entity Framework";
story1.Tags.Add(new Tag { Name = ".net", });
story1.Tags.Add(new Tag { Name = "database" });
Story story2 = new Story();
story2.Title = "Working with Managed DirectX";
story2.Tags.Add(new Tag { Name = ".net" });
story2.Tags.Add(new Tag { Name = "graphics" });
List<Story> stories = new List<Story>();
stories.Add(story1);
stories.Add(story2);
EfQuestionEntities db = new EfQuestionEntities();
Category category = (from c in db.Categories
where c.Name == "Programming"
select c).First();
foreach (Story story in stories)
{
foreach (Tag tag in story.Tags)
{
Tag currentTag = tag;
currentTag = GetTag(tag.Name, category, db);
}
db.Stories.AddObject(story);
}
db.SaveChanges();
}
public static Tag GetTag(string name, Category category, EfQuestionEntities db)
{
var dbTag = from t in db.Tags.Include("Category")
where t.Name == name
select t;
if (dbTag.Count() > 0)
{
return dbTag.First();
}
var cachedTag = db.ObjectStateManager.GetObjectStateEntries(EntityState.Added).
Where(ose => ose.EntitySet == db.Tags.EntitySet).
Select(ose => ose.Entity).
Cast<Tag>().Where(x => x.Name == name);
if (cachedTag.Count() != 0)
{
return cachedTag.First();
}
Tag tag = new Tag();
tag.Name = name;
tag.Category = category;
db.Tags.AddObject(tag);
return tag;
}
However, I get an exception about an object with the same EntityKey that is already present in the ObjectContext.
Also, if I remove the else statement I will get an exception about violating an FK constraint, so it seems like its Category attribute is set to null.

I 've had the same problem with EF. Here's what I ended up doing:
Instead of doing story1.Tags.Add(new Tag { Name = ".net", }) yourself, routed all Tag creation through a helper method like this: story1.Tags.Add(GetTag(".net")).
The GetTag method checks the tags in the context to see if it should return an existing entity, like you do. If it does, it returns that.
If there is no existing entity, it checks the ObjectStateManager to see if there are Tag entities added to the context but not already written to the db. If it finds a matching Tag, it returns that.
If it still has not found the Tag, it creates a new Tag, adds it to the context, and then returns it.
In essence this will make sure that no more than one instance of any Tag (be it already existing or just created) will be used throughout your program.
Some example code lifted from my project (uses InventoryItem instead of Tag, but you get the idea).
The check in step 3 is done like this:
// Second choice: maybe it's not in the database yet, but it's awaiting insertion?
inventoryItem = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(ose => ose.EntitySet == context.InventoryItems.EntitySet)
.Select(ose => ose.Entity)
.Cast<InventoryItem>()
.Where(equalityPredicate.Compile())
.SingleOrDefault();
if (inventoryItem != null) {
return inventoryItem;
}
If the Tag is not found in step 3, here's the code for step 4:
inventoryItem = new InventoryItem();
context.InventoryItems.AddObject(inventoryItem);
return inventoryItem;
Update:
It should be used like this:
Story story1 = new Story();
story1.Title = "Introducing the Entity Framework";
story1.Tags.Add(GetTag(".net", category, db));
story1.Tags.Add(GetTag("database", category, db));

Related

how to pass list of tags to anonymous function

This is my controller:
public JsonResult PostFunction(Post post,IEnumerable<int> MultipleTags)
{
post.PostedBy = User.Identity.GetUserId<int>();
post.PostedDate = DateTime.UtcNow;
foreach (var tagId in MultipleTags)
{
var tag = new Tag { TagId = tagId };
db.Tags.Attach(tag); // this avoids duplicate tags
post.Tags.Add(tag);
}
db.Posts.Add(post);
db.SaveChanges();
var usr = db.Users.FirstOrDefault(x => x.Id == post.PostedBy);
{
Message = post.Message,
// here, i am getting nothing but it should return back tagnames
TagName = string.Join(",", post.Tags.Select(t => t.TagName)),
PostedBy = post.PostedBy,
PostedByName = post.ApplicationUser.UserName,
};
return Json( ret,JsonRequestBehavior.AllowGet);
}
Howerever, data is properly got inserted into the database but this ret var is giving System.NullReferenceException. The line which is causing problem is TagName which should return comma seperated values.
If i Try something like this, then it works fine but it will not return the last inserted Post. means if in database, 7 posts are already there and after this new insertion, total no. of post are increased to 8 so it should return all 8 but it returns only 7 post excluding the latest one added.
var ret = from data in db.Posts.Include(x=> x.Tags)
.OrderByDescending(x => x.PostedDate).ToList()
select new
{
TagName = string.Join(",", data.Tags.Select(t => t.TagName)),
Now, how should i modify this anonymous function so that it should dynamically append the tags relating each post.
Actually, there is a textbox with multi select drop down. all the values from text box and dropdown goes to this controller but after submit button, the latest submitted post should be added to the page. but this anonymous function is causing error.it should pass correct data to knockout binding but the code get stuck here.I am sure there is nothing to do with knockout here.
I have also tried to put a variable with all the selected tags and then assign it to TagName like this:
var result = post.Tags.SelectMany(e => e.TagName);
// and in ret var,
TagName = string.Join(",", result)
but still getting NullReferenceException.
Please suggest me what should i try here.
You have to set the value for TagName where you create the tag:
foreach (var tagId in MultipleTags)
{
var tag = new Tag { TagId = tagId, TagName = "something" };
db.Tags.Attach(tag); // this avoids duplicate tags
post.Tags.Add(tag);
}

Iterating over two lists in c#

So i have a function that gets a list of students from a web service and also query the localdb for all the students in there. the data is placed in two different list. So i want to check to see if a new student already exists in the localdb List. if it does, update it and it if doesn't then add it. i unable to get it working . I am trying to perform this using LINQ, but i can't seem to get it working right. My LINQ skills are amateurish at best.
public async Task GetStudents()
{
String controllerName = "Students";
List<Students> newStudentData = await RunGetAsync<Students>(controllerName);
// get all the service types that already exists in the localStudent Db
List<Students> currentStudentData = db.Studentss.ToList();
foreach (Students existingStudents in currentStudentData)
{
foreach (Students newStudents in newStudentData)
{
IEnumerable<Students> selectStudents = from student in newStudentData // check if Students exist in the database
where student.Id == existingStudents.Id
select student;
if (selectStudents == null) // didn't find it, then add it
{
db.Students.Add(newStudents);
}
if (selectStudents != null) // found it , then update the informations
{
Students updatedStudents = new Students();
foreach (var field in selectStudents)
{
updatedStudents.FName = field.FName;
updatedStudents.LName = field.LName;
updatedStudents.ZipCode = field.ZipCode;
updatedStudents.AccessCode = field.AccessCode;
}
db.Entry(updatedStudents).State = System.Data.Entity.EntityState.Modified;
}
}
}
db.SaveChanges();
}
Thank you very much for your help.
you're looping more than you need :
foreach (Students newStudents in newStudentData)
{
var student = currentStudentData.FirstOrDefault(s => s.Id == newStudents.Id);
if(student == null)
{
//add
}
else
{
//update
}
}
with FirstOrDefault you can find out if it exists and get a reference to it at the same time, if it does.
You could use Intersect and Except like below:
//Find students that already exist to update
var updateStudents = currentStudentData.Intersect(newStudentData);
//Find new students to add
var addStudents = newStudentData.Except(currentStudentData);

How can I edit or add to a particular field without pull the all object

How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}

Entity Framework - Insert multiple complex objects at once

I am writing a parser for one of website, which have products connected to categories. I am trying to build my own database with these items.
I have decided to use Entity Framework, but I am new to this framework, so here's my problem:
During parsing i have multiple items with same category. But categories are kind of trees. I mean, category have a reference to parentCategory.
During of parsing i have a list of category inheritance f.e : category1 -> category1.1 -> category1.1.1
Each product I parse and add to database need to verify if that category exist and go through category inheritance to create non existing categories.
Code looks like this:
Category parentCategory = null;
foreach (var sCategory in categories)
{
var currentCategory = d.CategorySet.SingleOrDefault(category => category.Name == sCategory && category.Parent == parentCategory);
if (currentCategory == null)
{
currentCategory = new Category(){Name = sCategory,Parent = parentCategory};
if(parentCategory != null)
d.Entry(parentCategory).State = EntityState.Unchanged;
}
parentCategory = currentCategory;
}
But in this case, SingleOrDefault LinQ does not work because of exception:
Unable to create a constant value of type 'DataBaseModel.Category'. Only primitive types or enumeration types are supported in this context.
I know that I should compare IDs of category, but in this case it needs to saveChanges into db every time I add sth to DB.
Is there any other possibility to handle that?
I have solved this issue by creating local Dictionaries of Categories and before usage, fill this dictionaries by data from database.
_categoriesDictionary.Clear();
foreach (var category in this.Container.CategorySet)
{
Category temp = category;
string fullCategoryString = "";
while (temp != null)
{
fullCategoryString = fullCategoryString.Insert(0, temp.Name + ";");
temp = temp.Parent;
}
_categoriesDictionary.Add(fullCategoryString, category);
}
And then when analyzing the record:
Category parentCategory = null;
string fullCatString = "";
foreach (var sCategory in categories)
{
fullCatString += sCategory + ";";
Category currentCategory;
if (!_categoriesDictionary.TryGetValue(fullCatString, out currentCategory))
{
currentCategory = new Category()
{
Name = sCategory,
Parent = parentCategory
};
this.Container.CategorySet.Add(currentCategory);
_categoriesDictionary.Add(fullCatString, currentCategory);
}
parentCategory = currentCategory;
}
result.Category = parentCategory;
This has another adventage from my point of view:
Its collecting data on start, and then do not query DB every time

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...').

Categories

Resources