Generic Repository EF 5 - Update Entity And It's Complex/Scalar/Navigation Properties - entity-framework

I'm trying to find an easy solution for updating an entity + the included properties in my solution. I've created an Generic Repository for my DBContext (database). It does update the parent entity, but not handling changes on the child properties. Is there a way to handle or track those changes?
Example code for updating child propery: (look at comment - example code)
[HttpPut]
public HttpResponseMessage PutBrand(Brand brand)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
try
{
// example code
brand.BrandSizes.FirstOrDefault().Name = "I'm a Test";
// add values
brand.State = State.Changed;
brand.DateChanged = DateTime.Now;
// update
brand = _brandService.UpdateBrand(brand);
// save
_brandService.SaveBrandChanges();
// signalR
Hub.Clients.All.UpdateBrand(brand);
return Request.CreateResponse<Brand>(HttpStatusCode.OK, brand);
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
Context:
public class ERPContext : DbContext
{
#region Catalog
public DbSet<Brand> Brands { get; set; }
public DbSet<BrandSize> BrandSizes { get; set; }
public DbSet<BrandSizeOption> BrandSizeOptions { get; set; }
public DbSet<BrandTierPrice> BrandTierPrices { get; set; }
#endregion Catalog
public ERPContext()
: base("db-erp")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Generic Repository:
public class ERPRepository<T> : IRepository<T> where T : class
{
#region Fields
private DbSet<T> _dbSet;
private DbContext _dataContext;
#endregion Fields
#region Ctor
public ERPRepository(DbContext dataContext)
{
if (dataContext == null)
{
throw new ArgumentNullException("dataContext", "dataContext cannot be null");
}
_dataContext = dataContext;
_dbSet = _dataContext.Set<T>();
}
#endregion Ctor
#region Methods
public T Add(T item)
{
return _dbSet.Add(item);
}
public T Delete(T item)
{
return _dbSet.Remove(item);
}
public T Update(T item)
{
var updated = _dbSet.Attach(item);
_dataContext.Entry(item).State = EntityState.Modified;
return updated;
}
public IQueryable<T> Query(params Expression<Func<T, object>>[] includes)
{
var query = _dbSet;
if (includes != null)
{
includes.ToList().ForEach(x => query.Include(x).Load());
}
return query;
}
public void SaveChanges()
{
_dataContext.SaveChanges();
}
#endregion Methods
}
Model:
public class Brand
{
#region Ctr
public Brand()
{
BrandSizes = new List<BrandSize>();
BrandTierPrices = new List<BrandTierPrice>();
}
#endregion Ctr
#region Properties
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? LogoId { get; set; }
public int DisplayOrder { get; set; }
public bool Deleted { get; set; }
public bool Locked { get; set; }
public State State { get; set; }
public DateTime DateChanged { get; set; }
public DateTime DateCreated { get; set; }
#endregion Properties
#region Mapping
public virtual Picture Logo { get; set; }
public virtual List<BrandSize> BrandSizes { get; set; }
public virtual List<BrandTierPrice> BrandTierPrices { get; set; }
#endregion Mapping
}
BrandService:
public partial class BrandService : IBrandService
{
#region Fields
private readonly IRepository<Brand> _brandRepository;
private readonly IRepository<BrandSize> _brandSizeRepository;
private readonly IRepository<BrandSizeOption> _brandSizeOptionRepository;
#endregion Fields
#region Ctor
public BrandService(IRepository<Brand> brandRepository, IRepository<BrandSize> brandSizeRepository, IRepository<BrandSizeOption> brandSizeOptionRepository)
{
_brandRepository = brandRepository;
_brandSizeRepository = brandSizeRepository;
_brandSizeOptionRepository = brandSizeOptionRepository;
}
#endregion Ctor
#region Methods
public virtual IEnumerable<Brand> GetAllBrands()
{
return _brandRepository.Query(x => x.BrandSizes);
//return _brandRepository.Query();
}
public virtual Brand GetBrandById(int id)
{
return _brandRepository.Query().Where(x => x.Id == id).FirstOrDefault();
}
public virtual Brand InsertBrand(Brand brand)
{
return _brandRepository.Add(brand);
}
public virtual Brand UpdateBrand(Brand brand)
{
return _brandRepository.Update(brand);
}
public virtual Brand DeleteBrand(Brand brand)
{
return _brandRepository.Delete(brand);
}
public virtual void SaveBrandChanges()
{
_brandRepository.SaveChanges();
}
#endregion Methods
}

Create IObjectWithState interface and State enum to track changes manually:
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
and implement the interface in every mapped entity
public class Brand:IObjectWithState
{ ....
[NotMapped]
public State State { get; set; }}
and add these two helper methods to convert the state and to apply the changes in the entire graph:
public static EntityState ConvertState(State state)
{
switch (state)
{
case State.Added :
return EntityState.Added;
case State.Deleted:
return EntityState.Deleted;
case State.Modified:
return EntityState.Modified;
case State.Unchanged:
return EntityState.Unchanged;
default:
return EntityState.Unchanged;
}
}
public static void ApplyStateChanges(this DbContext context)
{
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = StateHelpers.ConvertState(stateInfo.State);
}
}
and when update or insert any object edit the state of it like this object.State = State.Modified;
and then modify your insert or update method to be like this:
public void InsertOrUpdate(T entity, bool IsGraph)
{
if (((IObjectWithState)entity).State == State.Added)
{
dataContext.Entry(entity).State = System.Data.Entity.EntityState.Added;
}
else
{
dbset.Add(entity);
dataContext.Entry(entity).State = System.Data.Entity.EntityState.Modified;
}
//This method change the state of every changed object
if (IsGraph)
ApplyStateChanges(dataContext);
dataContext.Commit();
}

Related

not catch DbUpdateConcurrencyException?

i set a break point at var c = context.SaveChanges() , then update sql update [Student] set Name ='456' where PriKey =1 in Sqlserver Management Studio, continue, somstimes the program can not catch DbUpdateConcurrencyException, why happen this situation?
public class OfficeContext : DbContext
{
public DbSet<Student> Students { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
#"Data Source=192.168.31.215;User ID=hj;Password=hj123;Database=office;Integrated Security=false");
optionsBuilder.LogTo(Console.WriteLine);
}
[Table("Student")]
public class Student
{
[Column("PriKey")]
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Timestamp]
[Column("VerCol")]
public virtual byte[] RowVersion { get; set; }
[Column("Name")]
public string Name { get; set; }
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new OfficeContext()){
try
{
var a = context.Students.FirstOrDefault(x => x.Id == 1);
a.Name = "123";
var c = context.SaveChanges();
}
catch(DbUpdateConcurrencyException e)
{
Console.WriteLine(e.Message);
}
}
}
}

Including a Model from different DbContext

I am not sure how to achieve the relation between 2 DbContexts. PurchaseOrderDbContext is a Code first approach & AgencyDbContext is an existing database. How can I include the "Division" from AgencyDbContext based on PurchaseOrder DivisionId?
To start off here is a very simplified version of my code.
Purchase Order Model
namespace Website.Models.PurchaseOrders
{
public class PurchaseOrder
{
public int ID { get; set; }
public DateTime OrderDate { get; set; }
public string Name { get; set; }
public int DivisionId { get; set; }
public int StatusID { get; set; }
public Agency.Division Division { get; set; }
}
}
Division Model (this is in a different DbContext)
namespace Website.Models.Agency
{
public class Division
{
public int DivisionId { get; set; }
public string DivisionName { get; set; }
public string DivisionShortName { get; set; }
public string DivisionAbbrev { get; set; }
public int? DivisionDirectorEmpId { get; set; }
}
}
Agency DbContext
namespace Website.Models.Agency
{
public class AgencyDbContext : DbContext
{
public Agency DbContext(DbContextOptions<AgencyDbContext> options) : base(options)
{
}
public virtual DbSet<Division> Division { get; set; }
public virtual DbSet<Section> Section { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
}
PurchaseOrderDbContext
namespace Website.Models.PurchaseOrders
{
public class PurchaseOrderDbContext : DbContext
{
public PurchaseOrderDbContext(DbContextOptions<PurchaseOrderDbContext> options) : base(options)
{}
public DbSet<Status> Statuses { get; set; }
public DbSet<PurchaseOrder> PurchaseOrder { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
}
I get an the error InvalidOperationException: Lambda expression used inside Include is not valid. This is referring to the Include extension on Division.
var purchaseOrder = _context.PurchaseOrder
.Include(p => p.Division)
.Include(p => p.Status)
.OrderByDescending(p => p.OrderDate);
Thank you in advance!
Probably the only way to resolve is to make a query to the first context for items you are looking for, and then populate Division property with entries from second context
public class PurchaseOrderService
{
private readonly PurchaseOrderDbContext purchaseOrderDbContext;
private readonly AgencyDbContext agencyDbContext;
public PurchaseOrderService(PurchaseOrderDbContext purchaseOrderDbContext,
AgencyDbContext agencyDbContext)
{
this.purchaseOrderDbContext = purchaseOrderDbContext;
this.agencyDbContext = agencyDbContext;
}
public PurchaseOrder Get(int id)
{
var purchaseOrder = purchaseOrderDbContext.PurchaseOrder.FirstOrDefault(x => x.ID == id);
if (purchaseOrder == null)
{
return null;
}
purchaseOrder.Division = agencyDbContext.Division.FirstOrDefault(x => x.DivisionId == purchaseOrder.DivisionId);
return purchaseOrder;
}
}

Entity Framework one to one relation, disconnected entities

I want to work with disconnected objects. In my model a user can have or not an address.
First my model:
public class ApplicationUser : IdentityUser, IUser
{
public ApplicationUser() : base()
{
}
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime BornDate { get; set; }
public virtual SubscriptionSetting SubscriptionSetting { get; set; }
public virtual Address Address { get; private set; }
public void AddAddress(Address address)
{
this.Address = address;
}
}
public class Address
{
public Address(string id, string number, string type, string nom, string postalCode, string city, GPSCoordinates gps)
{
this.Id = id;
this.Number = number;
this.Type = type;
this.Nom = nom;
this.PostalCode = postalCode;
this.City = city;
this.GPSCoordinates = gps;
}
public virtual string Id { get; set; }
public virtual string Number { get; set; }
public virtual string Type { get; set; }
public virtual string Nom { get; set; }
public virtual string PostalCode { get; set; }
public virtual string City { get; set; }
public virtual GPSCoordinates GPSCoordinates { get; set; }
}
My context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
this.Database.Log = Console.Write;
}
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new SubscriptionSettingConfiguration());
modelBuilder.Configurations.Add(new ApplicationUserConfiguration());
modelBuilder.Configurations.Add(new AddressConfiguration());
base.OnModelCreating(modelBuilder);
}
}
My configuration:
public class ApplicationUserConfiguration : EntityTypeConfiguration<ApplicationUser>
{
public ApplicationUserConfiguration() : base()
{
HasRequired(u => u.Address).WithRequiredPrincipal();
Property(u => u.BornDate).HasColumnType("datetime2");
}
}
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
Property(m => m.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
HasKey(m => m.Id);
}
}
And my repo:
public virtual async Task<TEntity> InsertAsync(TEntity entity)
{
DbSet.Add(entity);
try
{
await _dbContext.SaveChangesAsync();
}
catch (DbEntityValidationException ex)
{
var sb = new StringBuilder();
foreach (var failure in ex.EntityValidationErrors)
{
sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
foreach (var error in failure.ValidationErrors)
{
sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
sb.AppendLine();
}
}
throw new DbEntityValidationException(
"Entity Validation Failed - errors follow:\n" +
sb.ToString(), ex
);
}
return entity;
}
Finally my unit test :
public class adress_repository_test
{
IRepository<Address> _addressRepository;
DbContext _context;
public adress_repository_test()
{
_context = new ApplicationDbContext();
_addressRepository = new Repository<Address>(_context);
}
[Fact]
public async Task insert_new_address_success()
{
GPSCoordinates gps = new GPSCoordinates(44.5, 41.2);
Address address = new Address("d0ead995-ca31-4950-bfd3-93e0ca82e37e", "BATC", "Allée", "beethoven", "60100", "creil", gps);
var result = await _addressRepository.InsertAsync(address);
Assert.NotEmpty(address.Id);
}
When the test run it throws an exception :
Violation of PRIMARY KEY constraint 'PK_dbo.Address'. Cannot insert
duplicate key in object 'dbo.Address'. The duplicate key value is
(d0ead995-ca31-4950-bfd3-93e0ca82e37e)
Sorry for this question but i haven't becareful about the config of my test, the connexion string is not present, and it's create a new database .
Steve is right the row is present in the BDD

Lazy loading not working in CodeFirst

[Table("Employee", Schema = "Master")]
public class Employee : Common
{
#region Properties
[Required]
[Key]
public int EmployeeID { get; set; }
public virtual Department Department { get; set; }
public int? DepartmentId { get; set; }
#endregion
}
[Table("Department", Schema = "Lookup")]
public class Department : Common
{
[Required]
[Key]
public int DepartmentId { get; set; }
[Required]
[StringLength(50)]
public string Value { get; set; }
public string Description { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
To get data
var employee = CemexDb.Employee.Where(w => w.EmployeeID == employeeId).FirstOrDefault();
when I fetch data, department always null
Please suggest the workaround
Here my context class that is in my code context class
public class CemexDb : DbContext
{
public virtual IDbSet<T> DbSet<T>() where T : class
{
return Set<T>();
}
public CemexDb() : base(ConfigurationManager.ConnectionStrings["CemexDb"].ConnectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
}
public CemexDb(string connectionString): base(connectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
}
public virtual void Commit()
{
base.SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<CemexDb>(null);
}
}
here is the access code
public class EmployeeService : RepositoryBase, IEmployeeService
{
public EmployeeService(IDatabaseFactory DbFactory): base(DbFactory)
{ }
}
Repository base class
public abstract class RepositoryBase
{
private CemexDb db;
/// <summary>
/// Holds a reference to the DatabaseFactory class used to manage connections to the database.
/// </summary>
protected IDatabaseFactory DatabaseFactory { get; private set; }
/// <summary>
/// Contains a reference to the <see cref="System.Data.Entity.DbContext"/> instance used by the repository.
/// </summary>
protected CemexDb CemexDb { get { return db ?? (db = DatabaseFactory.Get()); } }
/// <summary>
/// Initialises a new instance of the RepositoryBase class.
/// </summary>
/// <param name="DbFactory">A valid DatabaseFactory <see cref="Opendesk.Data.DatabaseFactory"/> object.</param>
public RepositoryBase(IDatabaseFactory DbFactory)
{
DatabaseFactory = DbFactory;
}
}
You must have proxy generation enabled and collection properties defined as virtual in order to have lazy loading working.
Also context should stay alive.
public class CemexDb : DbContext
{
public CemexDb()
{
this.Configuration.ProxyCreationEnabled = true;
}
public DbSet<Unicorn> Employees { get; set; }
}

EF 4.3 (Code First) - Custom ICollection Fails to catch new items

This is in reference to the question I asked regarding how to determine when items are added to the virtual ICollection property. As suggested, I have created a custom collection which inherits from Collection as shown below
public class EntityCollection<T> : Collection<T>
{
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
}
}
This is being used as
public class DbAppointment
{
public DbAppointment()
{
exceptionOcurrences = new EntityCollection<DbExceptionOcurrence>();
}
public virtual int AppointmentId { get; set; }
public virtual string Subject { get; set; }
public virtual string Body { get; set; }
public virtual DateTime Start { get; set; }
public virtual DateTime End { get; set; }
private ICollection<DbExceptionOcurrence> exceptionOcurrences;
public virtual ICollection<DbExceptionOcurrence> ExceptionOcurrences
{
get { return exceptionOcurrences; }
set { exceptionOcurrences = value; }
}
}
The problem is the only time the overridden InsertItem method seems to get called is if I initialise the database with a custom initialiser (example code below) and override the seed method!! What am I doing wrong?
Cheers
Abs
public class ContextInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
new List<DbAppointment>
{
new DbAppointment{ Subject = "hello", Body="world", Start=DateTime.Now, End=DateTime.Now.AddMinutes(30)},
}.ForEach(a => context.Appointments.Add(a));
new List<DbExceptionOcurrence>
{
new DbExceptionOcurrence{ExceptionDate=DateTime.Now}
}.ForEach(eo => context.ExceptionOcurrences.Add(eo));
base.Seed(context);
}
}