I'm working on a project that consists of multiple objects that I want to save to my database. I'm using a single context and a series of repository classes to access them.
When I try to save an entity, it seems to save all the virtual entities associated with it, even if that entity exists in the database already.
These are my classes:
public class Requirement
{
public int ID { get; set; }
public DateTime DateDue { get; set; }
public DateTime DateCompleted { get; set; }
public virtual Standard Standard { get; set; }
public virtual Project Project { get; set; }
}
public class Standard
{
public int ID { get; set; }
public int AgencyID { get; set; }
public string Name { get; set; }
public virtual Agency Agency { get; set; }
}
public class Project
{
public int ID { get; set; }
public bool Active { get; set; }
public virtual Agency Agency { get; set; }
public virtual Department Department { get; set; }
}
And this is the method I have for creating some data:
public class RequirementRepository
{
public static string CreateMockData()
{
StandardRepository stdRep = new StandardRepository();
ProjectRepository projRep = new ProjectRepository();
RequirementRepository reqRep = new RequirementRepository();
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
return sb.ToString();
}
}
And here is associated repository code:
public class ProjectRepository
{
public Project Find(int id)
{
using (var db = new MyContext())
{
return db.Projects
.Include(p => p.Agency)
.Include(p => p.Department)
.First(p => p.ID.Equals(id));
}
}
}
public class StandardRepository
{
public List<Standard> FindByAgencyID(int agencyID)
{
using (var db = new MyContext())
{
return db.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
}
public class RequirementRepository
{
public Requirement Save(Requirement requirement)
{
using (var db = new MyContext())
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = db.Requirements.Add(requirement);
}
else
{
db.Entry(requirement).State = EntityState.Modified;
}
db.SaveChanges();
return retVal;
}
}
}
When I run this method, I expect it to insert a number of new Requirements into the database with the project ID of 1 and a standard ID of whatever standard it's on. Instead, it creates a whole new project and a whole new standard for every requirement it adds, then assigns those IDs to the requirement.
Each context keeps track of the entities loaded, modified and the entities added.
Your repositories need to look something like this....
public class StandardRepository
{
MyContext _context;
public StandardRepository(MyContext context)
{
_context = context;
}
public List<Standard> FindByAgencyID(int agencyID)
{
return _context.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
public class RequirementRepository
{
MyContext _context;
public RequirementRepository(MyContext context)
{
_context = context;
}
public Requirement Save(Requirement requirement)
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = _context.Requirements.Add(requirement);
}
else
{
_context.Entry(requirement).State = EntityState.Modified;
}
_context.SaveChanges();
return retVal;
}
}
public class RequirementRepository
{
public static string CreateMockData()
{
using(MyContext context = new MyContext())
{
StandardRepository stdRep = new StandardRepository(context);
ProjectRepository projRep = new ProjectRepository(context);
RequirementRepository reqRep = new RequirementRepository(context);
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
}
return sb.ToString();
}
}
From my understanding, you shouldn't have to manually set the state of the object you have modified unless you have detached it from its context. EF keeps track of the object state.
I like to use something like this:
public abstract class EntityRepositoryBase<TEntity> : IDisposable, IEntityRepositoryBase<TEntity> where TEntity : class , IEntityWithId
{
protected EntityRepositoryBase()
{
Context = new SomeEntities();
}
public abstract ObjectSet<TEntity> EntityCollection { get; }
public SomeEntities Context { get; set; }
public TEntity GetById(int id)
{
return EntityCollection
.FirstOrDefault(x => x.Id == id);
}
public void Dispose()
{
Context.Dispose();
}
}
Then in the deriving repositories:
public class AnswerRepository : EntityRepositoryBase<AnswerEntity>, IAnswerRepository
{
public override ObjectSet<AnswerEntity> EntityCollection
{
get { return Context.AnswerEntities; }
}
}
I inject the Repositories into the relevant class using ninject but you should be able to get similar with:
using (var repo = new AnswerRepository())
{
// modifying via Context
var someEntity = repo.GetById(someId);
someEntity.Value = "1";
repo.Context.SaveChanges();
//modifying via repo
repo.Delete(anotherEntity);
}
and then doing what you need to do. The context is exposed via the interface IEntityRepositoryBase should you need to perform out-of-repository modifications and then SaveChanges() as well as any specific CRUD type methods in your repo. Once out of scope the object and the underlying connection will be closed.
Related
In relation of this answer, I'm trying to get multiple context to work by setting UseInMemoryDatabase to the same name. The test below fails, and the secondContext is empty.
What else do I need to do to share the same in memory database?
[Test]
public void MultipleContextTest()
{
var firstContext = new FirstContext(new DbContextOptionsBuilder<FirstContext>().UseInMemoryDatabase("DB").Options);
firstContext.Add(new Entity() {Name = "Test"});
firstContext.SaveChanges();
var secondContext = new SecondContext(new DbContextOptionsBuilder<SecondContext>().UseInMemoryDatabase("DB").Options);
Assert.AreEqual(1, secondContext.Entity.Count());
}
public class FirstContext : DbContext
{
public DbSet<Entity> Entity { get; set; }
public FirstContext(DbContextOptions options) : base(options)
{
}
}
public class SecondContext : DbContext
{
public DbSet<Entity> Entity { get; set; }
public SecondContext(DbContextOptions options) : base(options)
{
}
}
public class Entity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
I got it working by setting the InMemoryDatabaseRoot to the same object on all contexts.
private InMemoryDatabaseRoot _root;
private InMemoryDatabaseRoot Root
{
get
{
if(_root == null)
_root = new InMemoryDatabaseRoot();
return _root;
}
}
[Test]
public void MultipleContextTest()
{
var firstContext = new FirstContext(CreateOptions<FirstContext>());
firstContext.Add(new Entity() {Name = "Test"});
firstContext.SaveChanges();
var secondContext = new SecondContext(CreateOptions<SecondContext>());
Assert.AreEqual(firstContext.Entity.Count(), secondContext.Entity.Count());
}
private DbContextOptions CreateOptions<T>() where T : DbContext
{
return new DbContextOptionsBuilder<T>()
.UseInMemoryDatabase("DB", Root)
.Options;
}
Having the following context:
public class DemoContext : DbContext
{
private readonly string _connectionStringName = "DemoContext";
public DemoContext() : base("name=DemoContext")
{
//_connectionStringName = "DemoContext";
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
//Database.SetInitializer(new MigrateDatabaseToLatestVersion<DemoContext, Migrations.Configuration>(_connectionStringName));
Database.SetInitializer(new NullDatabaseInitializer<DemoContext>());
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Company> Companies { get; set; }
}
public class Employee
{
[Key]
public int EmployeeId { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class Company
{
[Key]
public int CompanyId { get; set; }
public string CompanyName { get; set; }
}
And this services:
public class EmployeeSvc
{
private readonly DemoContext _context;
public EmployeeSvc(Func<DemoContext> context)
{
_context = context();
}
public void Add(EmployeeAgg employee)
{
_context.Employees.Attach(new Employee()
{
Name = employee.Name,
DateOfBirth = employee.DateOfBirth
});
}
public void UpdateAll()
{
var employees = _context.Employees.ToList();
foreach (var employee in employees)
{
employee.Name = $"{Guid.NewGuid()}";
}
}
}
public class CompanySvc
{
private readonly DemoContext _context;
public CompanySvc(Func<DemoContext> context)
{
_context = context();
}
public void Add(CompanyAgg company)
{
_context.Companies.Attach(new Company()
{
CompanyName = company.CompanyName
});
}
public void UpdateAll()
{
var empresas = _context.Companies.ToList();
foreach (var empresa in empresas)
{
empresa.CompanyName = $"{Guid.NewGuid()}";
}
}
}
public class EmployeeAgg
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class CompanyAgg
{
public string CompanyName { get; set; }
}
Anf the following client code:
static void Main(string[] args)
{
var context = new DemoContext();
// ReSharper disable AccessToDisposedClosure
var employeeSvc = new EmployeeSvc(() => context);
var companySvc = new CompanySvc(() => context);
// ReSharper restore AccessToDisposedClosure
Console.WriteLine("Adding entities to context inside services");
employeeSvc.Add(new EmployeeAgg()
{
DateOfBirth = DateTime.Now.AddYears(-10),
Name = $"Employee name"
});
companySvc.Add(new CompanyAgg()
{
CompanyName = $"Company name"
});
employeeSvc.UpdateAll();
companySvc.UpdateAll();
context.SaveChanges();
context.Dispose();
Console.ReadLine();
}
Is it a good practice passing DbContext to application services as delegate in order to have a reference to a single instance and track all changes on it?
Maybe it's your example code, but there's no need here to use a delegate. You could as well just accept a DbContext and pass in the same instance:
public class EmployeeSvc
{
private readonly DemoContext _context;
public EmployeeSvc(DemoContext context)
{
_context = context();
}
// snip ...
}
public class CompanySvc
{
private readonly DemoContext _context;
public CompanySvc(DemoContext context)
{
_context = context();
}
// snip ...
}
var context = new DemoContext();
var employeeSvc = new EmployeeSvc(context);
var companySvc = new CompanySvc(context);
This would give you the exact same result, without needing to pass in a delegate and immediately invoking it.
I suppose however that maybe your actual code is a bit more complicated. If so, you should look into dependency injection and a DI-container.
A DI-container allows you to manage your dependencies and their lifecycles. A common lifecycle is the Singleton lifecycle, where the container will always construct your dependencies passing in the same instance.
if you need to injection your dbContext is better to use interface.
func<> is not suitable.
in Dependency injection interface is suitable.
(excusme for bad english)
I'm working with offline Master-Detail like this:
Currently I'm deleting collections and then adding them again. even though not all the items of the collection have been modified
Is that the proper way to do an update?
Edit:
Added Code and some modificatins based on comments:
CreateMasterFromView(){
Master aMaster = new Master();
aMaster.MasterId = View.Id // I set Id because I'm editing this master
aMaster.MasterValue = "Some value";
foreach (var detail in View.Details)
{
Detail aDetail = new Detail();
aDetail.DetailValue = View.DetailValue;
aDetail.MasterId = View.Id //
aMaster.Details.add(aDetail);
}
Save(aMaster);
}
Save (Master aMaster){
if (aMaster.MasterId != 0)
{
var oldDetails = Dba.Details.Where(detail=> detail.MasterId == aMaster.Id);
foreach (var oldDetail in oldDetails )
{
Dba.Details.Remove(oldDetail);
}
foreach (var detail in aMaster.Details)
{
Dba.Entry(detail).State = EntityState.Added;
}
}
Dba.Entry(aMaster).State = EntityState.Modified;
Dba.SaveChanges();
}
I'm not 100% sure but this should work :
Master.Details.Clear();
Dba.SaveChanges();
EF does a lot of tracking to its entities, don't try to work with EF as you'd work with SQL.
These examples below are useful when your entities come in non-Attached state (outside of Domain in multi-tier app its common scenario):
public class Master
{
public long MasterId { get; set; }
public object MasterValue { get; set; }
public virtual ICollection<Detail> Details { get; set; }
}
public class Detail
{
public long DetailId { get; set; }
public long MasterId { get; set; }
public object DetailValue { get; set; }
public Master Master { get; set; }
}
public class YourContext : DbContext
{
public DbSet<Master> Master { get; set; }
public DbSet<Detail> Detail { get; set; }
}
public class Test
{
public void ChangeMasterValue(Master master, object newValue)
{
using (var context = new YourContext())
{
context.Master.Attach(master);
master.MasterValue = newValue;
context.SaveChanges();
}
}
public void DeleteAllDetailsFromMaster(Master master)
{
using (var context = new YourContext())
{
context.Master.Attach(master);
master.Details.Clear();
context.SaveChanges();
}
}
public void AddDetailToMaster(Master master, Detail newDetail)
{
using (var context = new YourContext())
{
context.Master.Attach(master);
master.Details.Add(newDetail);
context.SaveChanges();
}
}
public void DeleteDetailFromMaster(Master master, Detail detailToDelete)
{
using (var context = new YourContext())
{
context.Master.Attach(master);
master.Details.Remove(detailToDelete);
context.SaveChanges();
}
}
public void UpdateMaster(Master master)
{
using (var context = new YourContext())
{
context.Master.Attach(master);
context.Entry(master).State=EntityState.Modified;
context.SaveChanges();
}
}
}
There are two ways to update an entity:
get old entity from db then update properties which are changed and
call save changes.
directly attach your new entity to db and call save changes. (no need to get old entity from db)
In your case it can be like this:
if (Master.MasterId != 0)
{
var oldDetails = Dba.Detail.Where(det => det.MasterId == Master.MasterId);
foreach (var olddetail in oldDetails)
{
// don't call remove here just update new values in those properties which are changed like this
}
//no need to set modified state because this olddetail is in current request
}
Dba.SaveChanges();}
Or second way:
if (Master.MasterId != 0)
{
Dba.Entry(Master).State = EntityState.Modified;
}
Dba.SaveChanges();
I am having a problem where when I try to save a new entity that has existing entities nested. Instead of creating a relationship with existing entities it is duplicating them.
This is roughly my model:
public class Record
{
public int ID { get; set; }
public string RecordValue { get; set; }
public virtual ICollection<AddressLine> AddressLines { get; set; }
}
public class AddressLine
{
public int ID { get; set; }
public string AddressLineValue { get; set; }
public virtual ICollection<AddressLineType> AddressLineTypes { get; set; }
}
public class AddressLineType
{
public int ID { get; set; }
public string AddressLineTypeValue { get; set; }
}
I don't want any duplicate AddressLineTypes added so in my code I am doing something like this:
public void button1_Click(object sender, EventArgs e)
{
Record r = new Record();
r.RecordValue = "Record value";
AddressLine al = new AddressLine();
al.AddressLineValue = "Address line value";
AddressLineType alt;
using (var db = new MyDbContext())
{
alt = db.AddressLineTypes.Single(x => x.Value == "TypeValue");
}
al.AddressLineTypes.Add(alt);
r.AddressLines.Add(al);
SaveRecord(r);
}
public void SaveRecord(Record r)
{
using (var db = new MyDbContext())
{
db.Records.Add(r);
db.SaveChanges();
}
}
I have hit a breakpoint before db.SaveChanges() and the AddressLineType ID is populated but it creates new entries in the database as if ID == 0.
How do I stop the existing AddressLineTypes duplicating on save?
Try using a single Context:
...
using (var db = new MyDbContext())
{
alt = db.AddressLineTypes.Single(x => x.Value == "TypeValue");
al.AddressLineTypes.Add(alt);
r.AddressLines.Add(al);
SaveRecord(r, db);
}
}
public void SaveRecord(Record r, MyDbContext db)
{
db.Records.Add(r);
db.SaveChanges();
}
Entity and Component has one-to-many relationship. Entity stored in db correctly, with all components. But when I trying load it back this Entity in Child method Components not loads. If i try use lazy loading, code failing on entity.GetComponent because of Entity.Components isn't initialized. After exception Components are initialized and has zero elements. If I disable lazy loading, Components are initialized and has zero elements. I wrote example for building one-to-many relationship and using lazy initialization, and it works fine.
public static void Main(string[] args)
{
Parent();
Child();
}
private static void Child()
{
using (var db = new EntitiesContext())
{
var entities = from entity in db.Entities
select entity;
foreach (Entity entity in entities)
{
Position pos = entity.GetComponent<Position>();
Core core = entity.GetComponent<Core>();
}
}
}
private static void Parent()
{
Entity entity = new Entity();
entity.AddComponent(new Position(10, 10));
entity.AddComponent(new ObjectName("Entity" + 1));
entity.AddComponent(new Core(100));
using (var db = new EntitiesContext())
{
db.Entities.Add(entity);
db.SaveChanges();
}
}
public class EntitiesContext : DbContext
{
public DbSet<TypeMaskPair> MappedTypes { get; set; }
public DbSet<Entity> Entities { get; set; }
public EntitiesContext()
: base("EntitiesDb")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasMany(entity => entity.Components)
.WithRequired(component => component.EntityObj)
.HasForeignKey(component => component.EntityId)
.WillCascadeOnDelete();
//this.Configuration.LazyLoadingEnabled = false;
}
}
public abstract class Component
{
[Key]
public int Id { get; set; }
[Required]
public int EntityId { get; set; }
[Required]
public virtual Entity EntityObj { get; set; }
private static DbMasksMapper componentsMap;
static Component()
{
componentsMap = new DbMasksMapper(typeof(Component));
}
public TypeMask GetMask()
{
return componentsMap.GetMask(this.GetType());
}
public static TypeMask GetMask<T>() where T : Component
{
return componentsMap.GetMask(typeof(T));
}
}
public class Entity
{
[Key]
public int Id { get; set; }
public virtual List<Component> Components { get; set; }
[NotMapped]
public TypeMask Mask { get; private set; }
public string TypeMaskString
{
get{ return Mask.ToString(); }
set{ Mask = new TypeMask(value); }
}
public Entity()
{
Components = new List<Component>();
Mask = new TypeMask();
}
public void AddComponent(Component component)
{
Components.Add(component);
component.EntityObj = this;
Mask |= component.GetMask();
}
public void DeleteComponent(TypeMask componentMask)
{
if (ContainsComponent(componentMask))
{
int removeIndex = Components.FindIndex(c => c.GetMask() == componentMask);
Components.RemoveAt(removeIndex);
Mask &= ~componentMask;
}
}
public Component GetComponent(TypeMask componentMask)
{
return Components.Find(c => c.GetMask() == componentMask);
}
public T GetComponent<T>() where T : Component
{
return (T) GetComponent(Component.GetMask<T>());
}
public bool ContainsComponent<T>() where T : Component
{
return ContainsComponent(Component.GetMask<T>());
}
public bool ContainsComponent(TypeMask componentMask)
{
return (Mask & componentMask) == componentMask;
}
}
class Position : Component
{
public Position(int x = 0, int y = 0)
{
X = x;
Y = y;
}
public int X { get; set; }
public int Y { get; set; }
}
class Cargo : Component
{
public Cargo(int capacity = 0)
{
Capacity = capacity;
}
public int Capacity { get; set; }
}
class Core : Component
{
public Core(int power = 0)
{
Power = power;
}
public int Power { get; set; }
}
class ObjectName : Component
{
public ObjectName(string name = "")
{
Name = name;
}
public string Name { get; set; }
}
I saw similar questions, but didn't found any answer.
Where is a mistake?
Solution
All works after I wrote default constructor for inherited components. But, I don't understand why constructor with default arguments doesn't suitable. Without any argument it should work like default constructor. Can anyone explain it? Seems I doing it wrong