OrmLite – Prevent automatic database changes when adding to or removing from a ForeignCollection - ormlite

Currently, I am using OrmLite and I have three objects A, B, and C. A contains a ForeignCollection of B, and B contains a ForeignCollection of C. Anytime I add an element to the ForeignCollections an insert statement is instantly executed. A delete statement is instantly executed anytime I remove an element.
I would like to add/remove without the statements being executed until I either create or update A. Is this possible?

I would like to add/remove without the statements being executed until I either create or update A. Is this possible?
There is no way to do this currently. The lazy-loaded collection doesn't even store items in itself so the idea of caching changes for later is not possible.
You could do this on your own however. You could keep a list of items in the entity and then override the dao.create(...) and dao.update(...) methods to call collection.addAll(...) if there are any cached items in your special collection.
private List<Item> foreignItemsToAdd;
...
private void addForeignItem(Item item) {
if (foreignItemsToAdd == null) {
foreignItemsToAdd = new ArrayList<Item>();
}
}
Then the dao would have something like:
#Override
public int create(ParentItem parent) throws SQLException {
if (parent.getForeignItemsToAdd() != null) {
parent.getForeignItems().addAll(parent.getForeignItemsToAdd());
}
return super.create(parent);
}

Related

querying in EntityObject

I have partial class TaxReportItem with partial method OnActualVolumeChanging(double value):
public partial class TaxReportItem
{
partial void OnActualVolumeChanging(double value)
{
if (Tax != null)
{
Payment = value*Tax.TaxRate;
}
}
}
In this method i want to get all collection of TaxReportItems that's present in context(something like this):
partial void OnActualVolumeChanging(double value)
{
var sum = 0.0;
if (Tax != null)
{
Payment = value*Tax.TaxRate;
foreach (var taxReportItem in ????)
{
sum += taxReportItem.Sum;
}
}
}
How can i achieve this?
This is actually quite hard because you should never need this. If you need this, design of your entity object is wrong and it is doing something which should be done elsewhere. Single TaxReportItem should never need to know about other tax report items and load them from database unless other items are dependent on this item (they form an aggregate). In such case you should have navigation property to dependent items in the principal one.
To follow your question. If you need to load other items you must have instance of the context to do that. You can either get instance used to load current item and use it to load other entities (bad solution) or you can create a new instance of the context and use it to load other entities (even worse solution).
As you can see from the linked article getting current context instance from the entity itself is not easy and it has some prerequisites which goes back to the first paragraph. It is hard because it is wrong approach.

EF 4 CTP 5: Trouble trying to remove an entity

I have created a model POCO class called Recipe; a corresponding RecipeRepository persists these objects. I am using Code First on top of an existing database.
Every Recipe contains an ICollection<RecipeCategory> of categories that link the Recipes and the Categories table in a many-to-many relationship. RecipeCategory contains the corresponding two foreign keys.
A simplified version of my controller and repository logic looks like this (I have commented out all checks for authorization, null objects etc. for simplicity):
public ActionResult Delete(int id)
{
_recipeRepository.Remove(id);
return View("Deleted");
}
The repository's Remove method does nothing but the following:
public void Remove(int id)
{
Recipe recipe = _context.Recipes.Find(id);
_context.Recipes.Remove(recipe);
_context.SaveChanges();
}
Howevery, the code above does not work since I receive a System.InvalidOperationException every time I run it: Adding a relationship with an entity which is in the Deleted state is not allowed.
What does the error message stand for and how can I solve the problem? The only thing I try to achieve is deleting an entity.
#Ladislav: I have replaced ICollection<RecipeCategory> by ICollection<Category>. Accidentially, ReSharper refactored away the virtual keyword.
However, the problem remains — I cannot delete a Category from a Recipe entity. The following code does not persist the deletion of the categories to the database:
private void RemoveAllCategoriesAssignedToRecipe()
{
foreach (Category category in _recipe.Categories.ToArray())
{
_recipe.Categories.Remove(category);
category.Recipes.Remove(_recipe);
}
_context.SaveChanges();
}
I have debugged the code and can confirm that the collections are modified correctly — that is, they contain no elements after the loop (I have also used the Clear() method). After calling SaveChanges(), they are populated again.
What am I doing wrong?
(Maybe it is important: I am using the Singleton pattern to only have one instance of the context.)
I was able to solve the problem the following way:
private void RemoveAllCategoriesAssignedToRecipe()
{
foreach (Category category in _recipe.Categories.ToArray())
{
Category categoryEntity = _categoryRepository.Retrieve(category.CategoryID);
var recipesAssignedToCategory = categoryEntity.Recipes.ToArray();
categoryEntity.Recipes.Clear();
foreach (Recipe assignedRecipe in recipesAssignedToCategory)
{
if (assignedRecipe.RecipeID == _recipe.RecipeID)
{
continue;
}
categoryEntity.Recipes.Add(assignedRecipe);
}
_context.Entry(categoryEntity).State = EntityState.Modified;
}
_recipe.Categories.Clear();
_context.SaveChanges();
}

Entity Framework - Auditing activity

My database has a 'LastModifiedUser' column on every table in which I intend to collect the logged in user from an application who makes a change. I am not talking about the database user so essentially this is just a string on each entity. I would like to find a way to default this for each entity so that other developers don't have to remember to assign it any time they instantiate the entity.
So something like this would occur:
using (EntityContext ctx = new EntityContext())
{
MyEntity foo = new MyEntity();
// Trying to avoid having the following line every time
// a new entity is created/added.
foo.LastModifiedUser = Lookupuser();
ctx.Foos.Addobject(foo);
ctx.SaveChanges();
}
There is a perfect way to accomplish this in EF 4.0 by leveraging ObjectStateManager
First, you need to create a partial class for your ObjectContext and subscribe to
ObjectContext.SavingChanges Event. The best place to subscribe to this event is inside the OnContextCreated Method. This method is called by the context object’s constructor and the constructor overloads which is a partial method with no implementation:
partial void OnContextCreated() {
this.SavingChanges += Context_SavingChanges;
}
Now the actual code that will do the job:
void Context_SavingChanges(object sender, EventArgs e) {
IEnumerable<ObjectStateEntry> objectStateEntries =
from ose
in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified)
where ose.Entity != null
select ose;
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
}
}
}
}
Code Explanation:
This code locates any Added or Modified entries that have a LastModifiedUser property and then updates that property with the value coming from your custom Lookupuser() method.
In the foreach block, the query basically drills into the CurrentValues of each entry. Then, using the Where method, it looks at the names of each FieldMetaData item for that entry, picking up only those whose Name is LastModifiedUser. Next, the if statement verifies that the LastModifiedUser property is a String field; then it updates the field's value.
Another way to hook up this method (instead of subscribing to SavingChanges event) is by overriding the ObjectContext.SaveChanges Method.
By the way, the above code belongs to Julie Lerman from her Programming Entity Framework book.
EDIT for Self Tracking POCO Implementation:
If you have self tracking POCOs then what I would do is that I first change the T4 template to call the OnContextCreated() method. If you look at your ObjectContext.tt file, there is an Initialize() method that is called by all constructors, therefore a good candidate to call our OnContextCreated() method, so all we need to do is to change ObjectContext.tt file like this:
private void Initialize()
{
// Creating proxies requires the use of the ProxyDataContractResolver and
// may allow lazy loading which can expand the loaded graph during serialization.
ContextOptions.ProxyCreationEnabled = false;
ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
// We call our custom method here:
OnContextCreated();
}
And this will cause our OnContextCreated() to be called upon creation of the Context.
Now if you put your POCOs behind the service boundary, then it means that the ModifiedUserName must come with the rest of data from your WCF service consumer. You can either expose this
LastModifiedUser property to them to update or if it stores in another property and you wish to update LastModifiedUser from that property, then you can modify the 2nd code as follows:
foreach (ObjectStateEntry entry in objectStateEntries) {
ReadOnlyCollection fieldsMetaData = entry.CurrentValues
.DataRecordInfo.FieldMetadata;
FieldMetadata sourceField = fieldsMetaData
.Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();
FieldMetadata modifiedField = fieldsMetaData
.Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();
if (modifiedField.FieldType != null) {
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
entry.CurrentValues.SetString(modifiedField.Ordinal,
entry.CurrentValues[sourceField.Ordinal].ToString());
}
}
}
Hope this helps.
There is a nuget package for this now : https://www.nuget.org/packages/TrackerEnabledDbContext
Github: https://github.com/bilal-fazlani/tracker-enabled-dbcontext

Entity Framework and Entity Tracker Problems

If I run the following code it throws the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
public void Save(Category category)
{
using(var db = new NorthwindContext())
{
if(category.CategoryID == 0)
{
db.AddToCategorySet(category);
}
else
{
//category.RemoveTracker();
db.Attach(category);
}
db.SaveChanges();
}
}
The reason is of course that the category is sent from interface which we got from GetById method which already attached the EntityChangeTracker to the category object. I also tried to set the entity tracker to null but it did not update the category object.
protected void Btn_Update_Category_Click(object sender, EventArgs e)
{
_categoryRepository = new CategoryRepository();
int categoryId = Int32.Parse(txtCategoryId.Text);
var category = _categoryRepository.GetById(categoryId);
category.CategoryName = txtUpdateCategoryName.Text;
_categoryRepository.Save(category);
}
I'm still learning Entity Framework myself, but maybe I can help a little. When working with the Entity Framework, you need to be aware of how you're handling different contexts. It looks like you're trying to localize your context as much as possible by saying:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
...
}
}
... within your data access method. Did you do the same thing in your GetById method? If so, did you remember to detach the object you got back so that it could be attached later in a different context?
public Category GetById(int categoryId)
{
using (var db = new NorthwindContext())
{
Category category = (from c in db.Category where Category.ID == categoryId select c).First();
db.Detach(category);
}
}
That way when you call Attach it isn't trying to step on an already-attached context. Does that help?
As you pointed out in your comment, this poses a problem when you're trying to modify an item and then tell your database layer to save it, because once an item is detached from its context, it no longer keeps track of the changes that were made to it. There are a few ways I can think of to get around this problem, none of them perfect.
If your architecture supports it, you could expand the scope of your context enough that your Save method could use the same context that your GetById method uses. This helps to avoid the whole attach/detach problem entirely, but it might push your data layer a little closer to your business logic than you would like.
You can load a new instance of the item out of the new context based on its ID, set all of its properties based on the category that is passed in, and then save it. This costs two database round-trips for what should really only need one, and it isn't very maintainable.
You can dig into the context itself to mark the Category's properties as changed.
For example:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
db.Attach(category);
var stateEntry = db.ObjectStateManager.GetObjectStateEntry(category);
foreach (var propertyName in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name)) {
stateEntry.SetModifiedProperty(propertyName);
}
db.SaveChanges();
}
}
This looks a little uglier, but should be more performant and maintainable overall. Plus, if you want, you could make it generic enough to throw into an extension method somewhere so you don't have to see or repeat the ugly code, but you still get the functionality out of it.

Convince entity context (EF1) to populate entity references

I have an entity with self reference (generated by Entity Designer):
public MyEntity: EntityObject
{
// only relevant stuff here
public int Id { get...; set...; }
public MyEntity Parent { get...; set...; }
public EntityCollection<MyEntity> Children { get...; set...; }
...
}
I've written a stored procedure that returns a subtree of nodes (not just immediate children) from the table and returns a list of MyEntity objects. I'm using a stored proc to avoid lazy loading of an arbitrary deep tree. This way I get relevant subtree nodes back from the DB in a single call.
List<MyEntity> nodes = context.GetSubtree(rootId).ToList();
All fine. But when I check nodes[0].Children, its Count equals to 0. But if I debug and check context.MyEntities.Results view, Children enumerations get populated. Checking my result reveals children under my node[0].
How can I programaticaly force my entity context to do in-memory magic and put correct references on Parent and Children properties?
UPDATE 1
I've tried calling
context.Refresh(ClientWins, nodes);
after my GetSubtree() call which does set relations properly, but fetches same nodes again from the DB. It's still just a workaround. But better than getting the whole set with context.MyEntities().ToList().
UPDATE 2
I've reliably solved this by using EF Extensions project. Check my answer below.
You need to assign one end of the relationship. First, divide the collection:
var root = nodes.Where(n => n.Id == rootId).First();
var children = nodes.Where(n => n.Id != rootId);
Now, fix up the relationship.
In your case, you'd do either:
foreach (var c in children)
{
c.Parent = root;
}
...or:
foreach (var c in children)
{
root.Children.Add(c);
}
It doesn't matter which.
Note that this marks the entities as modfied. You'll need to change that if you intend to call SaveChanges on the context and don't want this saved.
The REAL solution
Based on this article (read text under The problem), navigation properties are obviously not populated/updated when one uses stored procedures to return data.
But there's a nice manual solution to this. Use EF Extensions project and write your own entity Materilizer<EntityType> where you can correctly set navigation properties like this:
...
ParentReference = {
EntityKey = new EntityKey(
"EntityContextName.ParentEntitySetname",
new[] {
new EntityKeyMember(
"ParentEntityIdPropertyName",
reader.Field<int>("FKNameFromSP")
)
})
}
...
And that's it. Calling stored procedure will return correct data, and entity object instances will be correctly related to eachother. I advise you check EF Extensions' samples, where you will find lots of nice things.