Save Changes to EntityObject using Multiple Instances of One Context - c#

My Problem is the following.
I have a DataAccessLayer Project with a DalClass. In this Dal class I have many Methods for operations with Entity Framework. For example some pseudo-code to show how we do it.
class DalClass
{
public void SetEntityObject(EntityObject entityObject)
{
using (var context = new Entities())
{
context.EntityObjectSet.Attach(entityObject);
context.ChangeTracker.DetectChanges();
foreach (var entity in context.ChangeTracker.Entries())
{
entity.State = entityObject.EntityState;
}
context.SaveChanges();
}
}
public EntityObject GetEntitObject(Guid id)
{
EntityObject result;
using (var context = new Entities())
{
result = context.EntityObjectSet.Where(x => x.Id == Id);
}
return result;
}
}
Each time we do operations with EF we create a new instance context and do some work. My problem is that I have a method in my DalClass to get a List of EntityObjects Like this :
public List<EntityObject> GetEntityObjectsByIds(List<Guid> idList)
{
using (var context = new Entities())
{
var query = from entityObject in context.EntityObjectSet
where idList.Contains(entityObject.Id)
select entityObject;
return query.ToList();
}
}
After I return the List to my Business Layer class. I do some changes to each EntityObject, calculations, change some Property values and so on. After that I call a method from my BusinessLayer class to save the new edited List with this method:
public void UpdateEntityObjects(List<EntityObject> newEntityObjectList)
{
using (var context = new Entities())
{
var idList = entityObjectList.Select(x => x.Id).ToList();
var query = from entityObject in context.EntityObjectSet
where idList.Contains(entityObject.Id)
select entityObject;
var entityObjectList = query.ToList();
entityObjectList = newEntityObjectList;
context.ChangeTracker.DetectChanges();
foreach (var entity in context.ChangeTracker.Entries())
{
entity.State = EntityState.Modified;
}
context.SaveChanges();
}
}
Problem
I can not save newEntityObjectList that I Edited in my BusinessLayer. Obviously this is not working.
entityObjectList = newEntityObjectList;
So how is the best way to save the newEntityObjectList ?
Problem
I have to fier this query twice
var query = from entityObject in context.EntityObjectSet
where idList.Contains(entityObject.Id)
select entityObject;
Solution to 2. Problem could be if I use one Context instance for GetEntityObjectsByIds() and UpdateEntityObjects(). But in all our Dal Classes we avoided that. Even if I use one instance of Context class, this does not solve Problem 1. So how can I save newEntityObjectList ?

Well, my DAL class would most definetely be something like this:
class EntityObjectDAL
{
private ObjectContext/DbContext context = new ObjectContext/DbContext();
public List<EntityObject> GetObjects() { return context.EntityObjects.ToList(); }
// Tracked by context. Just change it in BL
public void SaveObjects() { context.SaveChanges(); }
// Just call SaveChanges(), no need for anything special...
public void InsertObject(EntityObject eo) { context.EntityObjects.Add(eo); }
public void UpdateObject(EntityObject eo) { context.Entry(eo).State = EntityState.Modified; }
}
Keep it simple...

Related

Problem with Two DbSet of two types A and B with B that inherits from A

I have two class Profile and JobProfile, JobProfile inherit from Profile.
I am using Entity Framework 6 and in my dbContext I have two DbSet :
public DbSet<JobProfile> JobProfiles { get; set; }
public DbSet<Profile> Profiles { get; set; }
I have also a DAL :
public class DAL : IDisposable
{
BddContext db;
public DAL()
{
db = new BddContext();
}
public async Task<ObservableCollection<Profile>> GetProfiles()
{
var oc = new ObservableCollection<Profile>();
var profiles = await(db.Profiles
.Include("FolderInformationAction")
.Include("FolderInformationStore")
.ToListAsync());
foreach (var item in profiles)
{
oc.Add(item);
}
return oc;
}
public async Task<ObservableCollection<JobProfile>> GetJobProfiles()
{
var oc = new ObservableCollection<JobProfile>();
var profiles = await (db.JobProfiles
.Include("FolderInformationAction")
.Include("FolderInformationStore")
.ToListAsync());
foreach (var item in profiles)
{
oc.Add(item);
}
return oc;
}
public void Dispose()
{
db.Dispose();
}
}
And when I call GetProfiles, it returns Profiles and JobProfiles elements but when I call GetJobProfiles, it returns only JobProfiles elements.
So the question is, how can I make a difference between the two DbSet ? it seems that they are linked.
I think TPH does that by default. Use this to trim down or maybe TPT will do what you want by definition.
var profiles = await(db.Profiles
.Where(q => !(q is JobProfile)
.Include("FolderInformationAction")
.Include("FolderInformationStore")
.ToListAsync());

C# Entity Framework Generic class -> reload all child entities

I currently have a multi one-to-many relationship hierarchy database tblProjects->tblLines->tblGroups->tblStations etc. And an Entity framework 6 model.
These entity framework classes all implement a base class "tblBase":
public abstract class TblBase : INotifyPropertyChanged
{
private int _id;
public int ID
{
get
{
return _id;
}
set
{
_id = value;
NotifyPropertyChanged();
}
}
private Nullable<int> _coid;
public Nullable<int> COID
{
get
{
NotifyPropertyChanged();
return _coid;
}
set
{
_coid = value;
NotifyPropertyChanged();
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged();
}
}
I have a treeview that allows me to select any node as the parent type, and currently I have a method for each type that allows me to reload all the child entities.
I would like to see how this could be made generic:
private async static Task<bool> RefreshLinesAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
//Scan through all DB child entities and reload their DB values
foreach (var SubEntity in DBContext.tblLines.Where(x => x.ProjectID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
//Continue to child entities method
await RefreshGroupsAsync(ThisUser, DBContext, SubEntity);
}
return true;
}
private async static Task<bool> RefreshGroupsAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.tblGroups.Where(x => x.LineID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
await RefreshStationsAsync(ThisUser,DBContext, SubEntity);
}
return true;
}
The only method I can see useful is Set(), although it does not provide a Where() method, which is critical since I do not want to retrieve the entire table.
You can make your functions generic. They maybe like this one:
private async static Task<bool> RefreshLinesAsync<TEntity>(LocalUser ThisUser, ProjectEntities DBContext, TEntity Entity) where TEntity : TblBase
{
List<TEntity> NonExistingNodes = new List<TEntity>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.Set<TEntity>().Where(x => (x as TblBase).ProjectID == PassedEntity.ID).ToList()) {
//Your other code here...
}
}
The where clause in function definition, make you sure that this method can be called only with subclasses of TblBase.
EDIT:
I forgot to mention that you need to cast SubEntity as TblBase inside foreach loop to use it...
EDIT (in response of comments):
If you need to get all TblBase subclasses from your entity, you cannot make your function so generic if you keep them in separate tables: It will became hardly mantainable when you have to add more subclasses.
I suggest you to use a single table through Table Per Hierarchy (see this article in MSDN) changing TblBase from abstract to concrete class, then you can get all of them this way:
var allSubClassEntities = DBContext.Set<TblBase>();

Property not updated after SaveChanges (EF database first)

First of all, I would like to say that I read the related posts (notably EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way?, and Entity Framework 4.1 RC (Code First) - Entity not updating over association).
However, I could not solve my problem. I am quite new to Entity Framework so I guess I must have misunderstood those posts answers.
Anyway I would be really grateful is someone could help me understand because I am quite stuck.
I have two tables :
Person
Item with a nullable PersonId and a Type
An item can have an owner, or not.
Consequently, Person has an Items property which is an IEnumerable of Item.
A person can have one only Item by type.
If the person wants to change, he can replace his current item by any other of the same type in his items :
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Here is the repositories structure and the AddOrUpdate method :
public class PersonRepo : RepositoryBase<Person>
{
...
}
public class RepositoryBase<TObject> where TObject : class, IEntity
{
protected MyEntities entities
{
get { return UnitOfWork.Current.Context; }
}
public virtual void AddOrUpdate(TObject entity)
{
if (entity != null)
{
var entry = entities.Entry<IEntity>(entity);
if (Exists(entity.Id))
{
if (entry.State == EntityState.Detached)
{
var set = entities.Set<TObject>();
var currentEntry = set.Find(entity.Id);
if (currentEntry != null)
{
var attachedEntry = entities.Entry(currentEntry);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
set.Attach(entity);
entry.State = EntityState.Modified;
}
}
else
entry.State = EntityState.Modified;
}
else
{
entry.State = EntityState.Added;
}
}
}
}
This works pretty well and the old and the new items' PersonId properties are correctly updated in database.
However, if I check person.Items after the SaveChanges(), the old item still appears instead of the new one and I need it to be correct in order to update the page's controls values.
Although I read the posts with the same issue I could not resolve it...
I tried lots of things, notably calling entities.Entry(person).Collection(p => p.Items).Load() but got an exception each time I tried.
If somebody has any idea please feel free, I can add some more code if needed.
Thanks a lot !
EDIT : UnitOfWork
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyEntities _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities Context
{
get
{
if (_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
if (_dbContext != null)
_dbContext.Dispose();
}
}
Two solutions that worked
Solution 1 (reload from context after SaveChanges)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
if (person != null)
person = service.GetCurrentPerson(Request.QueryString["id"]);
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Solution 2 (update database AND property)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
person.Items.Remove(oldItem);
person.Items.Add(newItem);
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
How about refreshing your context to make sure you have the latest db changes after the .SaveChanges() method. Pass in the entity to be refreshed an call Refresh on the context:
((IObjectContextAdapter)_dbContext).ObjectContext.Refresh(RefreshMode.StoreWins, entityPassed);
Or leave the Commit() method as is and use a more dynamic approach something like:
var changedEntities = (from item in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Deleted
| EntityState.Modified
| EntityState.Unchanged)
where item.EntityKey != null
select item.Entity);
context.Refresh(RefreshMode.StoreWins, changedEntities);
The RefreshMode.StoreWins simply indicates that the database (store) takes priority and will override client (in-memory) changes.
If the Refresh method does not work, you can consider the following:
public void RefreshEntity(T entity)
{
_dbContext.Entry<T>(entity).Reload();
}
Or if all else fails, keep it simple and Dispose of your DbContext once you're done with each transaction (In this case after SaveChanges() has been called). Then if you need to use results after a commit, treat it as a new transaction and, instantiating a fresh DbContext and load your necessary data again.
Use Transection for example.
It's working fine.
public class UnitOfWork : IUnitOfWork
{
public readonly DatabaseContext _context;
private readonly IDbTransaction _transaction;
private readonly ObjectContext _objectContext;
public UnitOfWork(DatabaseContext context)
{
_context = context as DatabaseContext ?? new DatabaseContext ();
this._objectContext = ((IObjectContextAdapter)this._context).ObjectContext;
if (this._objectContext.Connection.State != ConnectionState.Open)
{
this._objectContext.Connection.Open();
this._transaction = _objectContext.Connection.BeginTransaction();
}
}
public int Complete()
{
int result = 0;
try
{
result = _context.SaveChanges();
this._transaction.Commit();
}
catch (Exception ex)
{
Rollback();
}
return result;
}
private void Rollback()
{
this._transaction.Rollback();
foreach (var entry in this._context.ChangeTracker.Entries())
{
switch (entry.State)
{
case System.Data.Entity.EntityState.Modified:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
case System.Data.Entity.EntityState.Added:
entry.State = System.Data.Entity.EntityState.Detached;
break;
case System.Data.Entity.EntityState.Deleted:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
}
}
}
public void Dispose()
{
if (this._objectContext.Connection.State == ConnectionState.Open)
{
this._objectContext.Connection.Close();
}
_context.Dispose();
}
}

Reexecuting IQueryable on different DbContext instance?

I'm creating a repository layer to encapsulate DbContext.
I would like to expose a fluent interface for building query, and dispose and create a new DbContext every time after a query request is sent:
var repo = new EntityRepository();
repo = EntityRepository.FilterByAge(30).FilterByGender("Male");
var people = repo.GetPeople();
// GetPeople() should send the request, get the result, then dispose
// the old DbContext and create a new one
repo = repo.FilterByOccupation("Programmer");
var programmers = repo.GetPeople();
// the age and gender filters should still apply here
Currently in my EntityRepository, I have a private DbContext and a IQueryable. In the Filter methods, I append the Linq methods onto the IQueryable. Then after finishing a request, I do:
db.Dispose();
db = new EntityContext();
But this does not work when I try to do another request. It says the DbContext has been disposed.
How do I keep the same query for a new DbContext?
I ended up keeping a list of "filters", which are anonymous functions that takes in IQueryable and return IQueryable. Then I apply them on a short-live DbContext :
Repository:
private IList<Func<IQueryable<Person>, IQueryable<Person>>> filters;
public Repository FilterByAge(int age)
{
var _filters = new List<Func<IQueryable<Person>, IQueryable<Person>>>(filters);
_filters.Add(q => q.Where(e => e.Age == age));
return new Repository(_filters);
}
public Repository OrderByName()
{
var _filters = new List<Func<IQueryable<Entity>, IQueryable<Entity>>>(filters);
_filters.Add(q => q.OrderBy(e => e.Name));
return new Repository(_filters);
}
private IQueryable<Person> ApplyFilters(AppContext db)
{
var result = db.People;
foreach (var filter in filters)
{
result = filter(result);
}
return result;
}
public IEnumerable<Person> GetPeople()
{
IEnumerable<Person> people;
using (var db = new AppContext())
{
people = ApplyFilters(db).ToList();
}
return people;
}
Usage
private Repository repo = new Repository();
var peopleOfThirty = repo.FilterByAge(30);
var orderedByName = peopleOfThirty.OrderByName();
if (wantOrder)
{
return peopleOfThirty.GetPeople();
}
else
{
return orderedByName.GetPeople();
}
It works for my purposes. However, please let me know if there is any problem doing it this way. Thanks!

Generic DBContext for Where Query

so I am trying to make a generic function for a where query, not using repository
so it is possible to do something like this?
public IEnumerable<T> Something<T>(int authorId) where T : class
{
return Vmsb.Set<T>().Where(c => c.AuthorId== authorId);
}
now I can't because it dont know what c.AuthorId is
Create an interface IHaveAuthor and specify it on partial classes with this property:
public interface IHaveAuthor
{
int AuthorId { get; set; }
}
//Note that the interface is already implemented in auto-generated part.
//Or if it's Code First, just specify it directly on your classes.
public partial class Book : IHaveAuthor
{
}
public partial class Article : IHaveAuthor
{
}
Then point the interface under the generic type where constraint:
public IEnumerable<T> GetAuthorPublicationsOf<T>(int authorId)
where T : class, IHaveAuthor
{
return Vmsb.Set<T>().Where(c => c.AuthorId == authorId);
}
And the usage:
var authorBooks = query.GetAuthorPublicationsOf<Book>(authorId);
var authorArticles = query.GetAuthorPublicationsOf<Article>(authorId);
Adding on to Olexander's answer, since EF recommends you use the Unit of Work pattern, I usually don't assume a DbContext in my methods - I pass in the most generic object possible instead. Also just as a matter of style, I like to return the interface.
EDIT Updated to include Olexander's important fix to use IQueryable instead of IEnumerable.
So my method signature would look like:
public IQueryable<IHaveAuthor> Something(int authorId, IQueryable<IHaveAuthor> items)
{
return items.Where(c => c.AuthorId == authorId);
}
So calling this would be a bit different than your current calls to it - presumably something like:
var db = new MyDbContext();
var items = db.Books;
var itemForAuthor1 = Something(1, items);
Otherwise your "Something" method isn't terribly flexible - it assumes a single existing DbContext on your current object which might not be a safe assumption (since it's only supposed to live as long as this small chunk of work, whatever it is), you can't chain it with other commands, etc.
Diego hope my code helps u.
protected List<T> ListAll<T>() where T : class
{
using (MyDbContext db = new MyDbContext ())
{
return db.Set(typeof(T)).Cast<T>().AsNoTracking<T>().ToList();
}
}
protected T ListAllById<T>(int id) where T : class
{
using (MyDbContext db = new MyDbContext ())
{
return db.Set(typeof(T)).Cast<T>().Find(id);
}
}
protected void InsertObj(Object obj)
{
using (MyDbContext db = new MyDbContext())
{
db.Set(obj.GetType()).Add(obj);
db.SaveChanges();
}
}
protected void UpdateObj(Object obj)
{
try
{
using (MyDbContext db = new MyDbContext())
{
db.Set(obj.GetType()).Attach(obj);
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
}
}
catch (System.Exception ex)
{
System.Windows.Forms.MessageBox.Show(" " + ex.Message);
}
}
protected void DeleteObj(Object obj)
{
using (MyDbContext db = new MyDbContext ())
{
db.Set(obj.GetType()).Attach(obj);
db.Entry(obj).State = EntityState.Deleted;
db.SaveChanges();
}
}

Categories

Resources