In my code I save a lot of classes to the database over and over using the same methodology. So I tried to abstract it, shown below:
public long Save<T>(BaseViewInterface<T> view, AppsRTSEntities dbContext)
{
try
{
if (view.IsNew())
{
return Create(view, dbContext);
}
else
{
T tmp = view.ToPersistent(dbContext);
dbContext.T.Attach(tmp);
dbContext.Entry(tmp).State = System.Data.Entity.EntityState.Modified;
dbContext.SaveChanges();
return tmp.ID;
}
}
catch (DbEntityValidationException exc)
{
throw new DbEntityValidationException(GetExceptionMsg(exc), exc);
}
catch (DbUpdateException exc)
{
throw new DbEntityValidationException(GetExceptionMsg(exc), exc);
}
}
T is the autogenerated partial class Microsoft creates. So for instance my view would be
public class MyView : BaseViewInterface<My>
My would be the auto-generated class that is created as part of the db context. So my question is simple. What am I doing wrong here? How can I make this generic saving work?
Edit: I can't just modify AppsRTSEntities to include a definition for T, as it's also auto-generated.
Intstead of doing dbContext.T.Attach(tmp); you need to use the DbContext.Set<T>() method to get the generic DbSet datatype.
else
{
T tmp = view.ToPersistent(dbContext);
//Could also do "dbCondext.Set<T>().Attach(tmp);" but I split it for this example
DbSet<T> t = dbCondext.Set<T>();
t.Attach(tmp);
dbContext.Entry(tmp).State = System.Data.Entity.EntityState.Modified;
dbContext.SaveChanges();
return tmp.ID;
}
Related
I discovered one problem while creating my project. If someone refer to the issue I will be grateful.
In my project I use a layered model. The repository layer (data access layer) that communicates with the database (DB) and the service layer (business logic layer) in which services and objects are implemented (data transfer object).
As a result, there is a problem with the dbSet.Update method. When object (obj) comes into Update method as parameter, during the method call of the _db.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified or _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj) in the case of the first user's update (like from "view.xaml") obj is update and changes saving in the database (because dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) returns null and obviously my obj gets into the _db.Update method). In case of a repeated user's update (in the "view.xaml" view) object obj -- when it gets into the _db.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj) method, it isn't take account, as the data context already tracks it and in the _db.Update method gets an object from dbSet.Local.FirstOrDefault(i => i.Id == obj.Id).
Everything would be fine if this object in dbSet.Local was updated according to the type that comes from the user. However, this is not the case, it is tracked but not changed when the user edits its properties. It is not tracked properly rather due to the fact that I use services and, accordingly, data transfer object entities.
In view of the foregoing, I have a question.
How to make to update entity (by a new modified object) that are tracked, or how to manually assign the necessary object in dbSet.Local to replace the one stored there? or how to make Microsoft.EntityFrameworkCore.ChangeTracking not track changes to my objects in any way?
In order to make changes not tracked I used the QueryTrackingBehavior.NoTracking parameter for the DbContextOptionsBuilder entity, but this only helps on the first load, and tracking is still used when the data is updated further.
I also used the dbSet.Local.Clear() methods, but this is a bad idea, as data updating due to the deletion of the previous data from the database (like delete 20 rows from table and add one updated).
public abstract class GenericRepository<T, TKey> : IGenericRepository<T, TKey> where T : class, IEntity, new()
{
protected DbContext context;
protected DbSet<T> dbSet;
private readonly object _lock = new object();
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual IQueryable<T> GetAll()
{
lock (_lock)
return dbSet;
}
public virtual async Task<T> GetAsync(TKey id)
{
try
{
return await dbSet.FindAsync(id);
}
catch (Exception exc)
{
throw exc;
}
}
public async Task AddAsync(T obj)
{
try
{
//dbSet.Local.Clear();
await dbSet.AddAsync(obj);
//context.Entry(obj).State = EntityState.Added;
}
catch (Exception exc)
{
throw exc;
}
}
public async void Delete(TKey id)
{
//context.Entry(obj).State = EntityState.Deleted;
//context.Remove(obj);
try
{
T obj = await GetAsync(id);
dbSet.Remove(obj);
//dbSet.Remove(dbSet.Find(1504));
}
catch (Exception exc)
{
throw exc;
}
}
public void Update(T obj)
{
try
{
context.Entry(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj).State = EntityState.Modified;
//dbSet.Update(dbSet.Local.FirstOrDefault(i => i.Id == obj.Id) ?? obj);
//dbSet.Update(obj).State = EntityState.Modified;
//dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
public async Task SaveAsync()
{
try
{
await context.SaveChangesAsync();
}
catch (Exception exc)
{
throw exc;
}
}
public virtual IQueryable<T> Where(Expression<Func<T, bool>> predicate)
{
lock (_lock)
return dbSet.Where(predicate);
}
//public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
//{
// return dbSet.Where(predicate);
//}
}
}
Data saving method occurs at a higher level (service level). In the View user updates data (for example, adjusts the property one of the rows of the DataGrid), then in a context binding to this View, specifically in the ViewModel, calls the binding property (by INotifyPropertyChanged), which initiates call of the method that is responsible for the CRUD operation, then this method (On_bt_CategoryUpdate_Command) calling method from private field of genericService which is responsible for updating data in the database.
public class MainViewModel : ViewModel
{
#region Fields
private IGenericService<ContractDTO, int> _contractService;
#endregion
#region Commands
#endregion
#region Properties
private object GetList;
public object _GetList { get => GetList; private set => Set(ref GetList, value); } //Property to initialize DataGrid
private ContractDTO _contractDTO;
public ContractDTO SelectedItem { get => _contractDTO; set => Set(ref _contractDTO, value); } //Property with the data of the selected row in the DataGrid
#endregion
public SpesTechViewModel(IGenericService<ContractDTO, int> contractService)
{
_contractService = contractService;
}
#region Commands function
private bool Can_bt_CategoryUpdate_Command() => true;
private void On_bt_CategoryUpdate_Command()
{
try
{
_contractService.UpdateAsync(SelectedItem); //Method that updates and saves the data in the database
}
catch (Exception exc) { MessageBox.Show(exc.Message); }
}
#endregion
#region Function
void GetByFilter<T>(IFilterModel<T> filter, IGenericService<T, int> service) where T : class, new()
{
lock (_lock)
_GetList = service.Where(filter.Predicate()).Result.ToObservableCollection();
}
#endregion
}
_contractService.UpdateAsync(SelectedItem):
public async Task<DbObjectDTO> UpdateAsync(DbObjectDTO obj)
{
try
{
DbObject dbObject = mapper.Map<DbObject>(obj);
repository.Update(dbObject);
await repository.SaveAsync();
return mapper.Map<DbObjectDTO>(dbObject);
}
catch (Exception exc) { throw exc; }
}
the first thing in this method: using AutoMapper, the DTO object is converted into a DAL object;
second: data is update using the repository layer (code above, public void Update(T obj) method); third: saving to the database; fourth: reverse mapping.
If I'm trying to do smth like this in the Update(T obj) method of GenericRepository<T, TKey>:
public void Update(T obj)
{
try
{
dbSet.Update(obj);
//dbSet.Local.FirstOrDefault(i => i.Id == obj.Id)
}
catch (Exception exc) { throw exc; }
}
On the second update of the same Entity in dbSet with a new changes of T obj I get an exception:
{"The instance of entity type 'Contract' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."}
It's unusual to be so heavily coupled to the local cache. The normal pattern is to call DbSet.Find(id), and update the properties of the entity that it returns.
var entity = await dbSet.FindAsync(new object[] { id }, cancellationToken);
var entry = context.Entry(entity);
entry.CurrentValues.SetValues(obj);
await context.SaveChangesAsync(cancellationToken);
This pattern has a few benefits compared to what you are doing:
If no entity exists with that id, you have a chance to inform the caller
Entity Framework handles the caching
Entity Framework only updates the properties that have actually changed
If the entity is not in the cache, then Entity Framework does have to go to the database. In certain scenarios, e.g. if your entity has a large binary property and you plan to overwrite it with a new value, you might want to avoid loading the existing values from the database and just skip to the update part.
var local = dbSet.Local.FirstOrDefault(i => i.Id == obj.Id);
if (local != null)
{
var entry = context.Entry(local);
entry.CurrentValues.SetValues(obj);
}
else
{
var entry = dbSet.Attach(obj);
entry.State = EntityState.Modified;
}
await context.SaveChangesAsync(cancellationToken);
This is an exception to the general use case, I recommend you follow the normal pattern in most cases and only use this if performance dictates it.
Goal: get id of the inserted generic entity by using async with ASP.NET Core
Problem: what part am I missing in order to achieve the goal?
Code:
public async Task<int> AddAsync(T entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _context.AddAsync(entity);
await _context.SaveChangesAsync();
return <Id Here>
}
catch (Exception)
{
throw new Exception($"{nameof(entity)} could not be saved");
}
}
Thank you!
It doesn't work to use id.
It doesnt work to use where T.
You can create an interface with Id property and have all your entities implement them.
public interface IEntity
{
public int Id { get; set; }
}
And add a where clause to the generic class.
public class GenericRepository<T> where T : IEntity
{
public async Task<int> AddAsync(T entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _context.AddAsync(entity);
await _context.SaveChangesAsync();
return entity.Id;
}
catch (Exception)
{
throw new Exception($"{nameof(entity)} could not
be saved");
}
}
}
Maybe try using reflection:
return (int)entity.GetType().GetProperty("Id").GetValue(entity, null);
Assuming the naming is Id for every table. Otherwise, maybe you can pass the name of the prop as a parameter:
public async Task<int> AddAsync<T>(T entity, string paramName) where T : class
{
// ...
return (int)entity.GetType().GetProperty(paramName).GetValue(entity, null);
}
I have a lot of classes with this structure as you can see:
public class OrganizationUserRepository : IOrganizationUserRepository
{
private DataContext _ctx;
public OrganizationUserRepository(DataContext ctx)
{
_ctx = ctx;
}
public bool Add(OrganizationUser entity)
{
try
{
_ctx.OrganizationUsers.Add(entity);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
public bool Edit(OrganizationUser entity)
{
try
{
OrganizationUser Edited = _ctx.OrganizationUsers.Where(i => i.Id == entity.Id).First();
_ctx.Entry(Edited).CurrentValues.SetValues(entity);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
public bool Remove(string id)
{
try
{
Int64 Id = Int64.Parse(id);
OrganizationUser obj = _ctx.OrganizationUsers.Where(i => i.Id == Id).First();
_ctx.OrganizationUsers.Remove(obj);
_ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// TODO log this error
return false;
}
}
}
The db context in constructor is injected by ninject .as you can see it just one of my classes .and i have multi classes like this in another services that use a single DB .(WCF Service).But i get this error in my wcf tracelog :
ExecuteReader requires an open and available Connection. The connection's current state is open.
I am using EF code first .
I found this Wrap DbContext db = new DbContext() inusing statement. And i want to know should i use this ,if Yes how can i change my class structure to use using in my code ?
I used public Readonly DbContext .i just remove readonly and everything work fine.
This is my query where I'm returning an IEnumerable<CreditCardTransaction> to iterate through.
public partial class CreditCardTransaction
{
public static IEnumerable<CreditCardTransaction> GetUnprocessedTransactions()
{
try
{
using (var context = new SuburbanEntities())
{
return from trans in context.CreditCardTransactions
where trans.IsPublished == false
select trans;
}
}
catch (Exception ex)
{
Logging.Log("An error occurred.", "GetUnprocessedTransactions",Apps.ServicesConfig, ex);
return null;
}
}
}
This is where I am modifying those transactions once I have processed them:
public void ProcessFile()
{
try
{
_client = new TruckServiceClient();
_globalSetting = new GlobalSetting();
var unprocesstransactions = CreditCardTransaction.GetUnprocessedTransactions();
foreach (var creditCardTransaction in unprocesstransactions)
{
creditCardTransaction.IsPublished = ProcessTransaction(creditCardTransaction);
}
}
catch (Exception ex)
{
Logging.Log("An error occurred.", "ProcessCreditCardTransactions.ProcessFile", Apps.RemoteServices, ex);
}
}
I am modifying the transactions here:
creditCardTransaction.IsPublished = ProcessTransaction(creditCardTransaction);
But once I have saved them, can I update the entity directly or do I need to create another method where I pass this information back in?
The problem you don't have access to the context. Here you have some examples how to do:
https://github.com/geersch/EntityFrameworkObjectContext
If you're developing Asp.Net app, you'll have some drawbacks illustreted in this article:
http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx#managing-objectcontext-instantiation
I have a problem with some simple code, I'm refactoring some existing code from LINQ to SQL to the Entity Framework. I'm testing my saves and deletes, and the delete is really bugging me:
[TestMethod]
public void TestSaveDelete()
{
ObjectFactory.Initialize(x =>
{
x.For<IArticleCommentRepository>().Use<ArticleCommentRepository>();
});
PLArticleComment plac = new PLArticleComment();
plac.Created = DateTime.Now;
plac.Email = "myemail";
plac.Name = "myName";
plac.Text = "myText";
plac.Title = "myTitle";
IArticleCommentRepository acrep = ObjectFactory.GetInstance<IArticleCommentRepository>();
try
{
PortalLandEntities ple = new PortalLandEntities();
int count = ple.PLArticleComment.Count();
acrep.Save(plac);
Assert.AreEqual(ple.PLArticleComment.Count(), count + 1);
//PLArticleComment newPlac = ple.PLArticleComment.First(m => m.Id == plac.Id);
//ple.Attach(newPlac);
acrep.Delete(plac);
Assert.AreEqual(ple.PLArticleComment.Count(), count + 1);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Every time i try to run this code, I get an exception in the delete statement, telling me that its not contained within the current ObjectStateManager.Please note that both my Save and delete looks like this:
public void Delete(PLCore.Model.PLArticleComment comment)
{
using (PortalLandEntities ple = Connection.GetEntityConnection())
{
ple.DeleteObject(comment);
ple.SaveChanges();
}
}
public void Save(PLCore.Model.PLArticleComment comment)
{
using (PortalLandEntities ple = Connection.GetEntityConnection())
{
ple.AddToPLArticleComment(comment);
ple.SaveChanges();
}
}
and the connection thingy:
public class Connection
{
public static PortalLandEntities GetEntityConnection()
{
return new PortalLandEntities();
}
}
Any ideas on what i could do to make it work?
You cannot load an entity from one ObjectContext (in your case, an ObjectContext is an instance of PortalLandEntities) and then delete it from another ObjectContext, unless you detach it from the first and attach it to the second. Your life will be much, much simpler if you use only one ObjectContext at a time. If you cannot do that, you must manually Detach and then Attach first, all the while keeping track of which entities are connected to which ObjectContext.
How to use DI with your Connection : make it non-static.
public class Connection
{
private PortalLandEntities _entities;
public PortalLandEntities GetEntityConnection()
{
return _entities;
}
public Connection(PortalLandEntities entities)
{
this._entities = entities;
}
}
Then use a DI container per request. Most people do this via a controller factory.