Cannot update a complex property inside my object with Entity Framework - c#

I have an object A that has another object B inside. I am loading the using EntityFramework and modifying the object B but when I set the State to EntityState.Modified I have this error{"An entity object cannot be referenced by multiple instances of IEntityChangeTracker."}
This are my objects
public class Patient : IEntity, IObjectWithState
{
public virtual int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsMobileActived { get; set; }
public virtual MobilePatient MobilePatient { get; set; }
[NotMapped]
public State State { get; set; }
}
public class MobilePatient : IObjectWithState
{
public virtual int Id { get; set; }
public virtual int PatientPin { get; set; }
public virtual string SecurityAnswer { get; set; }
public virtual bool IsPinRemembered { get; set; }
public virtual PhysiologicalData PhysiologicalData { get; set; }
[NotMapped]
public State State { get; set; }
}
I have a repository and a unit of work and bascally using it I am loading the Patient object in this way.
... Patient patient = context.Patients.Find(id); ...
Then with the patient I am updating the MobilePatient
... patient.MobilePatient = NEWmobilePatient; ...
and then updating it with
PatientContext patientContext = (PatientContext)context;
patientContext.Entry(patient).State = EntityState.Modified;
My context only has the Patient dbset
public class PatientContext:BaseContext<PatientContext>, IPatientContext
{
public IDbSet<Patient> Patients { get; set; }
public void SetModified(object entity)
{
Entry(entity).State = EntityState.Modified;
}
public void SetAdd(object entity)
{
Entry(entity).State = EntityState.Added;
}
}
So I dont know what I am missing to update. When I am loading the Patient I am using the default lazy-load but how I have data in MobilePatient I am getting that object too.
Something that I think could be a good information is that I am using unit of work and repository and my application a disconnect application.
This my repository:
public class PatientRepository : IRepository<Patient>
{
private IPatientContext context=new PatientContext();
public PatientRepository(PatientContext context)
{
this.context = context;
}
public void Add(Patient patient)
{
context.SetAdd(patient);
}
public void Update(Patient patient)
{
context.SetModified(patient);
}
public void Delete(Patient entity)
{
throw new NotImplementedException();
}
public Patient FindById(int id)
{
Patient patient = context.Patients.Find(id);
return patient;
}
public IQueryable<Patient> Find(Expression<Func<Patient, bool>> predicate)
{
PatientContext patientContext = (PatientContext)context;
return patientContext.Set<Patient>().Where(predicate).AsQueryable<Patient>();
}
public IQueryable<Patient> FindAll()
{
return context.Patients;
}
}
I an this how I am using it in my services :
Patient patient = new Patient();
using (IPatientLoaderService patientLoaderService = AppManager.Instance.Database.CreatePatientLoaderService())
{
patient = patientLoaderService.LoadPatientById(patientId);
}
patient.MobilePatient =New_mobilePatient;
patient.State = State.Modified;
patient.Age = 40;
using (IPatientUpdaterService patientUpdaterService = AppManager.Instance.Database.CreatePatientUpdaterService())
{
patientUpdaterService.UpdatePatient(patient);
}
In my services I use the unit of work and the repositories
this is one of my services used in the code:
public class EntityFrameworkPatientUpdaterService: IPatientUpdaterService
{
private PatientRepository patientsRepository;
private EntityFrameworkUnitOfWork<PatientContext> unitOfWork;
public EntityFrameworkPatientUpdaterService()
{
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
public void UpdatePatient(Patient patient)
{ try
{
patientsRepository.Update(patient);
unitOfWork.Commit();
}
catch (Exception e)
{
//TODO: Log the error and evoid to throw another exception-DOR
unitOfWork.Dispose();
throw new Exception("Error on EntityFrameworkPatientUpdaterService.UpdatePatient. " +
e.Message);
}
finally
{
unitOfWork.Dispose();
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
Thank you for read this post
I am going to be adding more detail on how I am currently using my service. I think the problem is that I am trying to use tghe Onion Architecture and I am missing something.
public class PatientContext:BaseContext<PatientContext>, IPatientContext
{
public IDbSet<Patient> Patients { get; set; }
public void SetModified(object entity)
{
Entry(entity).State = EntityState.Modified;
}
}
public class PatientRepository : IRepository<Patient>
{
private readonly IPatientContext context;
public PatientRepository(PatientContext context)
{
this.context = context;
}
public void Update(Patient patient)
{
context.SetModified(_patient);
}
public Patient FindById(int id)
{
Patient patient = context.Patients.Find(id);
return patient;
}
}
public class EntityFrameworkPatientUpdaterService
{
private PatientRepository patientsRepository;
private EntityFrameworkUnitOfWork<PatientContext> unitOfWork;
public EntityFrameworkPatientUpdaterService()
{
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
public void UpdatePatient(Patient patient)
{ try
{
patientsRepository.Update(patient);
unitOfWork.Commit();
}
catch (Exception e)
{
//TODO: Log the error and evoid to throw another exception-DOR
unitOfWork.Dispose();
throw new Exception("Error on EntityFrameworkPatientUpdaterService.UpdatePatient. " +
e.Message);
}
finally
{
unitOfWork.Dispose();
}
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
//GEtting the patient and dispose everything,
Patient patient = new Patient();
using (IPatientLoaderService patientLoaderService = AppManager.Instance.Database.CreatePatientLoaderService())
{
patient = patientLoaderService.LoadPatientById(patientId);
}
//THEN Calling my services to update
using (IPatientUpdaterService patientUpdaterService = AppManager.Instance.Database.CreatePatientUpdaterService())
{
patientUpdaterService.UpdatePatient(patient);
}

If I interpret the exception message correctly, it means that you have two contexts, and both want the entry of your entity. This is not possible, only one context at the time can deal with a data row at a time.

Related

InvalidOperationException: Unable to resolve service for type 'DataAccessContext' while attempting to activate 'UnitOfWork'

I am getting the below error. I am using .Net Core web API.
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'CustomerManager.Db.DataAccessContext' while attempting to activate 'CustomerManager.Repository.UnitOfWork'.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
Api Controller
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
private ICustomersManager _customersManager = null;
public CustomerController(ICustomersManager customersManager)
{
_customersManager = customersManager;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var customers = await _customersManager.GetAll();
return Ok(customers);
}
}
Customer Model
public class Customers
{
public Customers()
{
Customers customer = this;
customer.CustomerBankDetails = new List<CustomerBankDetail>();
customer.CustomerContactDetails = new List<CustomerContactDetail>();
customer.CustomerFamilyDetails = new List<CustomerFamilyDetail>();
customer.CustomerPhotos = new List<CustomerPhoto>();
}
public int Id { get; set; }
public string CustomerNo { get; set; }
public string CustomerName { get; set; }
public string Gender { get; set; }
public DateTime? CustomerEntryDate { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Nationality { get; set; }
public bool? IsApproved { get; set; }
public bool IsActive { get; set; }
public bool? IsDeleted { get; set; }
public byte? SortedBy { get; set; }
public string Remarks { get; set; }
public virtual IEnumerable<CustomerBankDetail> CustomerBankDetails { get; set; }
public virtual IEnumerable<CustomerContactDetail> CustomerContactDetails { get; set; }
public virtual IEnumerable<CustomerFamilyDetail> CustomerFamilyDetails { get; set; }
public virtual IEnumerable<CustomerPhoto> CustomerPhotos { get; set; }
}
Customer Business Layer Code
public interface ICustomersManager
{
Task<List<Customers>> GetAll();
}
BLL Implementation
public class CustomersManager : ICustomersManager
{
private IUnitOfWork _unitOfWork = null;
public CustomersManager(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<List<Customers>> GetAll()
{
return await _unitOfWork.CustomerRepository.GetAllAsync();
}
}
Unit Of Work Interface
public interface IUnitOfWork : IDisposable
{
ICustomerRepository CustomerRepository { get; }
}
Unit of work implementation
public class UnitOfWork : IUnitOfWork
{
#region properties
private readonly DataAccessContext _context;
private ICustomerRepository _customerRepository;
public UnitOfWork(DataAccessContext context)
{
_context = context;
}
#endregion
public ICustomerRepository CustomerRepository =>
_customerRepository ?? (_customerRepository = new CustomerRepository(_context));
public void Dispose()
{
_customerRepository = null;
}
}
Customer Repository Interface
public interface ICustomerRepository : IRepository<Customers>
{
}
Customer Repository Implementation
public class CustomerRepository : BaseRepository, ICustomerRepository
{
public CustomerRepository(DataAccessContext objDataAccessContext) : base(objDataAccessContext)
{
}
public Task<List<Customers>> GetAllAsync()
{
return Task.Run(() =>
{
var objCustomerList = new List<Customers>();
ObjDbCommand.Parameters.Clear();
ObjDbCommand.AddInParameter("#Id", null);
try
{
ObjDbDataReader = ObjDataAccessContext.ExecuteReader(ObjDbCommand, "dbo.prGetAllCustomers", CommandType.StoredProcedure);
if (ObjDbDataReader.HasRows)
while (ObjDbDataReader.Read())
{
var objCustomer = new Customers();
BuildModel(ObjDbDataReader, objCustomer);
objCustomerList.Add(objCustomer);
}
}
catch (Exception ex)
{
throw new Exception("Error : " + ex.Message);
}
finally
{
if (ObjDbDataReader != null) ObjDbDataReader.Close();
ObjDataAccessContext.Dispose(ObjDbCommand);
}
return objCustomerList;
});
}
}
Generic Repository Interface
public interface IRepository<TEntity> where TEntity : class
{
Task<List<TEntity>> GetAllAsync();
}
Generic Repository Implementation
public class BaseRepository
{
protected readonly DataAccessContext ObjDataAccessContext;
protected readonly DbCommand ObjDbCommand;
protected DbDataReader ObjDbDataReader;
protected BaseRepository(DataAccessContext objDataAccessContext)
{
ObjDataAccessContext = objDataAccessContext;
ObjDbCommand = ObjDataAccessContext.GetCommand(true, IsolationLevel.ReadCommitted);
}
protected void BuildModel<T>(DbDataReader objDataReader, T item) where T : class
{
for (var inc = 0; inc < objDataReader.FieldCount; inc++)
{
var type = item.GetType();
var prop = type.GetProperty(objDataReader.GetName(inc));
var val = objDataReader.GetValue(inc) is DBNull || objDataReader.GetValue(inc).Equals(null) ||
string.IsNullOrEmpty(Convert.ToString(objDataReader.GetValue(inc)))
? null
: objDataReader.GetValue(inc);
prop?.SetValue(item, val, null);
}
}
}
Database Access Context using ADO.NET
public abstract class DataAccessContext
{
public DbCommand GetCommand(bool isTransaction, IsolationLevel isolationLevel)
{
var connectionString = DbConfiguration.ConnectionString;
return GetDbCommand(isTransaction, isolationLevel, connectionString);
}
public int ExecuteNonQuery(DbCommand objDbCommand, string textOrSpName, CommandType commandType)
{
try
{
objDbCommand.CommandType = commandType;
objDbCommand.CommandText = textOrSpName;
return objDbCommand.ExecuteNonQuery();
}
catch (DbException sqlEx)
{
throw new Exception("ExecuteNonQuery " + textOrSpName, sqlEx);
}
}
public int ExecuteNonQuery(DbCommand objDbCommand)
{
try
{
return objDbCommand.ExecuteNonQuery();
}
catch (DbException sqlEx)
{
throw new Exception("ExecuteNonQuery " + objDbCommand.CommandText, sqlEx);
}
}
public DbDataReader ExecuteReader(DbCommand objDbCommand, string textOrSpName, CommandType commandType)
{
try
{
objDbCommand.CommandType = commandType;
objDbCommand.CommandText = textOrSpName;
return objDbCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (DbException sqlEx)
{
throw new Exception("ExecuteReader " + textOrSpName, sqlEx);
}
}
public DbDataReader ExecuteReader(DbCommand objDbCommand)
{
try
{
return objDbCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (DbException sqlEx)
{
throw new Exception("ExecuteReader " + objDbCommand.CommandText, sqlEx);
}
}
public void Dispose(DbCommand objDbCommand)
{
if (objDbCommand.Connection != null)
{
objDbCommand.Connection.Dispose();
objDbCommand.Connection = null;
}
if (objDbCommand.Transaction != null)
{
objDbCommand.Transaction.Dispose();
objDbCommand.Transaction = null;
}
objDbCommand.Dispose();
objDbCommand = null;
}
private DbCommand GetDbCommand(bool bIsTransaction, IsolationLevel isolationLevel, string connectionString)
{
// retrieve provider invariant name from web.config
var providerInvariantName = string.Empty;
if (string.IsNullOrEmpty(providerInvariantName))
providerInvariantName = "System.Data.SqlClient";
// create the specific invariant provider
//DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance);
var objDbProviderFactory = DbProviderFactories.GetFactory(providerInvariantName);
var objDbConnection = objDbProviderFactory.CreateConnection();
if (objDbConnection == null) return null;
objDbConnection.ConnectionString = connectionString;
var objDbCommand = objDbProviderFactory.CreateCommand();
if (objDbCommand == null) return null;
objDbCommand.Connection = objDbConnection;
objDbConnection.Open();
if (bIsTransaction)
{
var objDbTransaction = objDbConnection.BeginTransaction(isolationLevel);
objDbCommand.Transaction = objDbTransaction;
return objDbCommand;
}
return objDbCommand;
}
}
Here is the Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
DbConfiguration.ServerName = Configuration["DbConnection:ServerName"];
DbConfiguration.DatabaseName = Configuration["DbConnection:DatabaseName"];
DbConfiguration.UserId = Configuration["DbConnection:UserId"];
DbConfiguration.Password = Configuration["DbConnection:Password"];
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//services.AddSingleton(typeof(DataAccessContext));
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
//Dependency Injection
//services.AddScoped(sp => sp.GetService(typeof(DataAccessContext)));
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<ICustomersManager, CustomersManager>();
}
How to solve this issue?
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type
'CustomerManager.Db.DataAccessContext' while attempting to activate
'CustomerManager.Repository.UnitOfWork'.
That is because you do not register DataAccessContext in Startup.cs.
For your DataAccessContext is abstract,note that you generally cannot register abstract classes because they cannot be instantiated.
Change like below:
public class DataAccessContext {}
Register like below:
services.AddScoped(typeof(DataAccessContext));
Nothing in the shown code snippets demonstrates why DataAccessContext should be an abstract class.
Make it a concrete class
public class DataAccessContext {
//...code omitted for brevity
}
Second, classes should depend on abstractions of services and not concretions. DataAccessContext should have a backing abstraction.
public interface IDataAccessContext {
DbCommand GetCommand(bool isTransaction, IsolationLevel isolationLevel);
int ExecuteNonQuery(DbCommand objDbCommand, string textOrSpName, CommandType commandType);
int ExecuteNonQuery(DbCommand objDbCommand);
DbDataReader ExecuteReader(DbCommand objDbCommand, string textOrSpName, CommandType commandType);
DbDataReader ExecuteReader(DbCommand objDbCommand);
Dispose(DbCommand objDbCommand);
//...
}
and
public class DataAccessContext: IDataAccessContext {
//...code omitted for brevity
}
Dependent classes should then explicitly depend on that abstraction
For example
public class UnitOfWork : IUnitOfWork {
private readonly IDataAccessContext _context;
private ICustomerRepository _customerRepository;
public UnitOfWork(IDataAccessContext context) {
_context = context;
}
//...omitted for brevity
And the abstraction and implementation registered with the DI container
services.AddSingleton<IDataAccessContext, DataAccessContext>();
so that it is aware of how to resolve the service while activating its dependents
services.AddScoped is bether than AddSingleton for this solve

Relationship doesn't updated in Entity Framework

When user update the property Curso (type Curso), this update does not work in Entity Framework. All data in object of type Turma are updated, but property Curso. Follow bellow my code:
This is the data received by PUT method in MVC controller:
{
"Id":1,
"DataVencimento":"2017-11-24T00:00:00",
"Nome":".Net MVCss",
"Turno":"Tarde",
"Curso":
{
"Id":1,
"Nome":"teste",
"Duracao":2,
"ValorAnuidade":5888.88,
"QtdParcelas":2,
"ValorParcela":22.22,
"ValorMatricula":22.22,
"Disciplinas":null,
"CorpoDocente":null,
"Documentos":null,
"Turmas":null
}
}
This is PUT method in MVC Controller:
[HttpPut]
public HttpResponseMessage Update(TurmaDto dto)
{
var response = new HttpResponseMessage();
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<ITurmaBLO>().To<TurmaBLO>();
ITurmaBLO blo = ninjectKernel.Get<ITurmaBLO>();
Turma t = Mapper.Map<TurmaDto, Turma>(dto);
if (!blo.Update(t))
{
response = Request.CreateResponse(HttpStatusCode.NotFound, "Turma não encontrada.");
}
else
{
response = Request.CreateResponse(HttpStatusCode.OK, t);
}
return response;
}
This is Class TurmaBLO:
public class TurmaBLO : GenericaBLO<Turma>, ITurmaBLO
{
private IKernel ninjectKernel = new StandardKernel();
private ITurmaDAO _dao;
public TurmaBLO()
{
ninjectKernel.Bind<ITurmaDAO>().To<TurmaDAO>();
_dao = ninjectKernel.Get<ITurmaDAO>();
}
public override bool Add(Turma e)
{
return _dao.Add(e);
}
public override bool Update(Turma e)
{
return _dao.Update(e);
}
public override List<Turma> GetAll()
{
return _dao.GetAll();
}
public override Turma Get(int id)
{
return _dao.Get(id);
}
}
This is Class GenericaBLO:
public class GenericaBLO<T> : IGenericaBLO<T> where T : class
{
public GenericaDAO<T> dao;
public virtual bool Add(T e)
{
dao = new GenericaDAO<T>();
return dao.Add(e);
}
public virtual bool Update(T e)
{
dao = new GenericaDAO<T>();
return dao.Update(e);
}
public virtual bool Delete(T e)
{
dao = new GenericaDAO<T>();
return dao.Delete(e);
}
public virtual List<T> GetAll()
{
dao = new GenericaDAO<T>();
return dao.GetAll();
}
public virtual T Get(int id)
{
dao = new GenericaDAO<T>();
return dao.Get(id);
}
public void ValidateForAdd()
{
throw new NotImplementedException();
}
public void ValidateForUpdate()
{
throw new NotImplementedException();
}
public void ValidateForDelete()
{
throw new NotImplementedException();
}
}
This is Class TurmaDAO:
internal class TurmaDAO : GenericaDAO<Turma>, ITurmaDAO
{
public override bool Add(Turma e)
{
base.Context.Curso.Attach(e.Curso);
return base.Add(e);
}
public override bool Update(Turma e)
{
base.Context.Curso.Attach(e.Curso);
return base.Update(e);
}
public override List<Turma> GetAll()
{
return base.Context.Turma.Include(c => c.Curso).Include(dt => dt.Descontos).ToList();
}
public override Turma Get(int id)
{
return base.Context.Turma.Include(c => c.Curso).Include(dt => dt.Descontos).SingleOrDefault(c => c.Id == id);
}
}
This is Class GenericaDAO:
public class GenericaDAO<T> : IGenericaDAO<T> where T : class
{
internal ApplicationDbContext Context { get; set; }
protected DbSet<T> DbSet { get; set; }
public GenericaDAO()
{
Context = new ApplicationDbContext();
DbSet = Context.Set<T>();
}
public virtual bool Add(T e)
{
try
{
Context.Entry(e).State = EntityState.Added;
Context.SaveChanges();
return true;
}
catch (Exception ex)
{
return false;
}
}
public virtual bool Update(T e)
{
try
{
Context.Entry(e).State = EntityState.Modified;
Context.SaveChanges();
return true;
}
catch (Exception ex)
{
return false;
}
}
public virtual bool Delete(T e)
{
try
{
Context.Entry(e).State = EntityState.Deleted;
Context.SaveChanges();
return true;
}
catch (Exception ex)
{
return false;
}
}
public virtual List<T> GetAll()
{
return DbSet.ToList();
}
public virtual T Get(int id)
{
return DbSet.Find(id);
}
}
This is Class TurmaDto:
public class TurmaDto
{
public int Id { get; set; }
public DateTime DataVencimento { get; set; }
public string Nome { get; set; }
public string Turno { get; set; }
public CursoDto Curso { get; set; }
}
This is Class Turma:
public class Turma
{
public int Id { get; set; }
public DateTime DataVencimento { get; set; }
public string Nome { get; set; }
public string Turno { get; set; }
public Curso Curso { get; set; }
}
You also need to call Update on the child entities, in your case Curso. So:
ITurmaBLO bloT = ninjectKernel.Get<ITurmaBLO>();
ICursoBLO blo = ninjectKernel.Get<ICursoBLO>();
Turma t = Mapper.Map<TurmaDto, Turma>(dto);
if(!(bloT.Update(t) && bloC.Update(t.Curso))){
response = Request.CreateResponse(HttpStatusCode.NotFound, "Turma não encontrada.");
}
If you need to only update the relation from Turma to Curso without changing the Curso, it's enough to set Turma.CursoId to the Id of the new Curso, assuming the relationship is mapped correctly in EF.

ef core .Include().Contains() null reference exception

I have my models setup like this...
public class Model1 : IEquatable<Model1>
{
public int Model1Id { get; set; }
public string Name1 { get; set; }
public Model2 Model2 { get; set; }
public int Model2Id { get; set; }
public bool Equals(Model1 other)
{
return this.Model2.Equals(other.Model2)
&& this.Name1 == other.Name1;
}
}
public class Model2 : IEquatable<Model2>
{
public int Model2Id { get; set; }
public string Name2 { get; set; }
public bool Equals(Model2 other)
{
return this.Name2 == other.Name2;
}
}
public class ModelContext : DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Model1>(b =>
{
b.HasOne(m1 => m1.Model2).WithMany().HasForeignKey(m1 => m1.Model2Id);
});
}
}
then I get a null reference exception when I do this...
static void Main(string[] args)
{
var myModel1 = new Model1
{
Name1 = "myName1",
Model2 = new Model2
{
Name2 = "myName2"
}
};
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<ModelContext>()
.UseSqlite(connection)
.Options;
//create database
using(var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
}
//add model objects
using (var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
ctx.Model1.Add(myModel1);
ctx.SaveChanges();
}
//check if exists
using(var ctx = new ModelContext(options))
{
//exception here
bool isExists = ctx.Model1.Include(m1 => m1.Model2).Contains(myModel1);
Console.WriteLine(isExists);
}
}
finally
{
connection.Close();
}
Console.ReadKey();
}
I'm expeting the Model2 instance of my m1 to be populated when I call the Include but it is still null.
but If I add AsEnumerable() to my query like..
ctx.Model1.Include(m1 => m1.Model2).AsEnumerable().Contains(model1);
then everything works fine.
EDIT:
my question is... why do I need to call AsEnumerable()? I was expecting it to work without calling AsEnumerable()..
The difference is one is an entityframe work call the other is linq to objects
Entity Framework Does not understand contains for a CLR Object
public void AddIfNotExists(Model1 model1)
{
//No Need for the include this is executed in sql, assuming the model 2
//property has already been included in your model1 this should work fine
if(false == _context.Model1.Any(x => x.Name1 == model1.Name1
&& x.Model2.Name2 == model1.Model2.Name2))
{
_context.Model1.Add(model1);
}
}
I made this based off of your logic, but chances are you really just want to check if model1.id is the the model1 set. But I have no Idea what your architecture is doing so this is what you probably want

Dependent Object Creation

Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);

Generic Repository Pattern Soft Delete

I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well.
I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.
I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.
I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.
this is an example of my current Generic Repo
public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
public String CurrentUser { get; set; }
private readonly MyObjectContext context;
private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
private IDbSet<T> entities;
private IDbSet<T> Entities
{
get { return entities ?? (entities = context.Set<T>()); }
}
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
var providerManager = new DataProviderManager(configuration);
var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
dataProvider.InitDatabase();
CurrentUser = userName;
}
public void Dispose()
{
//do nothing at the moment
}
public T GetById(Guid id)
{
return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}
public IQueryable<T> GetAll()
{
return Entities.Where(x => !x.IsDeleted);
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter).Where(x => !x.IsDeleted);
}
public void Delete(T entity)
{
if (configuration.HardDelete)
{
HardDelete(entity);
}
else
{
SoftDelete(entity);
}
}
private void HardDelete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
Entities.Attach(entity);
Entities.Remove(entity);
}
catch (DbEntityValidationException ex)
{
var msg = string.Empty;
foreach (var validationErrors in ex.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, ex);
throw fail;
}
}
private void SoftDelete(T entity)
{
entity.IsDeleted = true;
Update(entity);
}
}
Any help on this would be great.
Thanks
Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.
https://github.com/jbogard/EntityFramework.Filters
Here is an example how to use it.
public abstract class BaseEntity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
public string Name { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
public string Name { get; set; }
public int FooId { get; set; }
public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Registers and configures it first.
DbInterception.Add(new FilterInterceptor());
var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete",
e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
modelBuilder.Conventions.Add(softDeleteFilter);
}
}
Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
var bars = foo.Bars; // works on lazy loading
}
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
}
}
This filter is not needed anymore.
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}
As long as you have enabled it.
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
if (!configuration.HardDelete)
{
this.context.EnableFilter("SoftDelete");
}
}
I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property
public int DeletebyId(string Id)
{
var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
Ent.IsDeleted = 1;
}
and for hardDelete
_sitecontext.Set<TEntity>().Remove(Ent);
This is on ID but offcourse you can do it on EnTity as well

Categories