Updating entity stored outside current DbContext - c#

What's the recommended way of dealing with the following scenario in Entity Framework?
I have some session related data stored in a class called SessionData (which uses the Singleton pattern). One of the properties in this class is called Basket. The Basket class has a collection of BasketItems.
So when the basket is initialised, it gets added to the database and stored in the session:
var basket = new Basket();
using(var db = new DataContext())
{
db.Baskets.Add(basket);
db.SaveChanges();
}
SessionData.Current.Basket = basket;
Then later when a basket item is added to the basket:
using(var db = new DataContext())
{
var basketItem = new BasketItem() { initialisation here }
SessionData.Current.Basket.BasketItems.Add(basketItem);
db.SaveChanges();
}
This doesn't work because SessionData.Current.Basket isn't attached to the current DbContext. I've tried using:
db.Baskets.Attach(SessionData.Current.Basket)
This works the first time it's called, but fails with the following error on following calls:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
What's the recommended way of doing this update to an entity (and it's hierarchy) that is stored outside the usage of a current DbContext using block?

In the new db context, retrieve the basket from the database, and then create the new basket items.

The problem turned out to be because when I was initialising the properties of the new BasketItem, one of its properties (called Product) was tracked by another instance of DbContext. I hadn't spotted it, as I was thinking about the Basket class, not its sub-properties. I solved this by changing the query where I got the product instance from this:
var product = (from x in db.Products
where x.ID == basketRequest.ProductID
select x).FirstOrDefault();
to:
var product = (from x in db.Products.AsNoTracking()
where x.ID == basketRequest.ProductID
select x).FirstOrDefault();
Which gets the entity instance without it being tracked by the DbContext.
The link in the question that Daniel Auger mentioned in his comment also helped me understand the 'Insert or update pattern', which is what my question was really about. I disagree that the two questions are duplicates, as they're not really the same. Very helpful link though!

Related

Entity Framework duplicate entries in many to many relationship

I have already read many posts about the entity framework problem with many to many and its beeing a pain in the neck again.
Colaborador has an ICollection of Time and
Time has an Icollection of Colaborador
Some people say that it´s necessary to attach the child entity before Add in the context(didn´t work for me, Pk error).
I am using simple injector and my context is per request.
My associative table is mapped like this:
HasMany<Time>(c => c.Times)
.WithMany(t => t.Colaboradores)
.Map(ct =>
{
ct.MapLeftKey("ColaboradorId");
ct.MapRightKey("TimeId");
ct.ToTable("Receptor");
});
It creates the associative table in the database.
When i try to insert a Colaborador(entity), i add in its list some Times(Teams), add to DbContext and then SaveChanges().
When i do this, it creates a new Colaborador, insert correctly in the associative table(the ids) but also duplicate the Time.
var colaborador = Mapper.Map<ColaboradorViewModel, Colaborador>(colaboradorVm);
List<TimeViewModel> timesVm = new List<TimeViewModel>();
colaboradorVm.TimesSelecionados.ForEach(t => timesVm.Add(_serviceTime.BuscarPorId(t)));
colaborador.Times = Mapper.Map<ICollection<TimeViewModel>, ICollection<Time>>(timesVm);
The function BuscarPorId does the Find method and returns a Time.
I have figured out that if i call the Add command, the entity will mark the child´s state as Added as well, but if i attempt to attach the Time or change it´s state to Unchanged, i get a primary key error...
foreach (var item in colaborador.Times)
{
lpcContext.Set<Time>().Attach(item);
//lpcContext.Entry(item).State = EntityState.Unchanged;
}
Is there any way of tell to entity framework to not insert a specific child? So only the main and associative table are populated?
Mapper.Map creates new Time objects which are not attached to your context, so you must attach them as Unmodified, but attaching them causes another error due to duplicate PK because your context is already tracking the original copies of the Time entities. Using the Find method will retrieve these tracked and locally cached entities.
Find and use the entity already attached to your context:
Instead of:
colaborador.Times = Mapper.Map<ICollection<TimeViewModel>, ICollection<Time>>(timesVm);
Use:
var times = new List<Time>();
var dbSet = lpcContext.Set<Time>();
foreach( var t in timesVm )
{
var time = dbSet.Find( t.Id );
if( null == time )
{
time = Mapper.Map<TimeViewModel, Time>( t );
}
times.Add( time );
}
collaborador.Times = times;

How should I disable Entity Framework table reference(foreign) list from each objects?

I'm using Sqlite database and System.Data.SQLite 1.0.92
There is 2 table here:
Table Person:
PersonId
PersonName
Table Student:
StudentId
PersonId(reference table Person FK)
StudentNo
Now every time I get the Persons Collection in EF5:
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
But I don't need it. Of course that's just an example, There is a lot of big table has so many references, it always has performance problems here because of that.
So I'm trying to do not let it in my result. So I change it:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
}
Now fine, because AllPersons.student collection will always be null
But now I found: If I get Person and Student together:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
AllStudents = ctx.Student.ToList();
}
Now the reference still include in.
So Is there anyway to don't let the reference include in any time in this situation?
Thank you.
Update
For some friends request, I explain why I need it:
1: When I convert it to json it will be a dead loop. even I already use Json.net ReferenceLoopHandling, the json size very big to crash the server.(if no references, it's just a very small json)
2:Every time I get the client data and need to save, it will display exception about model state, until I set it to null.
Example:
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
Person model= ThisIsAModel();
model.students = null; // This is a key, I need set the students collection references to null , otherwise it will throw exception
ctx.Entry(model).State = EntityState.Modified;
ctx.SaveChanges();
}
3: This is More important problem. I already get all data and cache on the server. But It will let the loading time very long when server start. (because the data and references are so many, that is the main problem), I don't know I'll meet what kind of problem again....
public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
// There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....
PersonsCache = ctx.Persons.ToList();
StudentsCache= ctx.Student.ToList();
}
The Problem
As you said, when you load both of Parent and Child lists even when LazyLoading is disabled, and then look in parent.Childs you see child items has been loaded too.
var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();
What happened? Why childs are included in a parent?
The childs under a parent entity, are those you loaded using db.YourChildSet.ToList(); Exactly themselves; In fact Entity Framework never loads childs for a parent again but because of relation between parent and child in edmx, they are listed there.
Is that affect Perforemance?
According to the fact that childs only load once, It has no impact on perforemance because of loading data.
But for serialization or something else's sake, How can I get rid of it?
you can use these solutions:
Solution 1:
Use 2 different instance of YourDbContext:
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
Now when you look in parent.Childs there is no Child in it.
Solution 2:
use Projection and shape your output to your will and use them.
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
.Select(x=>new /*Model()*/{
Property1=x.Property1,
Property2=x.Property2, ...
}).ToList();
This way when serialization there is nothing annoying there.
Using a custom Model class is optional and in some cases is recommended.
Additional Resources
As a developer who use Entity Framework reading these resources is strongly recommended:
Performance Considerations for Entity Framework 4, 5, and 6
Connection Management
I'll focus on your third problem because that seems to be your most urgent problem. Then I'll try to give some hints on the other two problems.
There are two Entity Framework features you should be aware of:
When you load data into a context, Entity Framework will try to connect the objects wherever they're associated. This is called relationship fixup. You can't stop EF from doing that. So if you load Persons and Students separately, a Person's Students collection will contain students, even though you didn't Include() them.
By default, a context caches all data it fetches from the database. Moreover, it stores meta data about the objects in its change tracker: copies of their individual properties and all associations. So by loading many objects the internal cache grows, but also the size of the meta data. And the ever-running relationship fixup process gets slower and slower (although it may help to postpone it by turning off automatic change detection). All in all, the context gets bloated and slow like a flabby rhino.
I understand you want to cache data in separate collections for each entity. Two simple modifications will make this much quicker:
Evade the inevitable relationship fixup by loading each collection by a separate context
Stop caching (in the context) and change tracking by getting the data with AsNoTracking.
Doing this, your code will look like this:
public List<Person> PersonsCache;
public List<Student> StudentsCache;
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
PersonsCache = ctx.Persons
.AsNoTracking()
.ToList();
}
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
StudentsCache= ctx.Student
.AsNoTracking()
.ToList();
}
The reason for turning off ProxyCreationEnabled is that you'll get light objects and that you'll never inadvertently trigger lazy loading afterwards (throwing an exception that the context is no longer available).
Now you'll have cached objects that are not inter-related and that get fetched as fast as it gets with EF. If this isn't fast enough you'll have to resort to other tools, like Dapper.
By the way, your very first code snippet and problem description...
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
...suggest that Entity Framework spontaneously performs eager loading (of students) without you Include-ing them. I have to assume that your code snippet is not complete. EF never, ever automatically executes eager loading. (Unless, maybe, you have some outlandish and buggy query provider).
As for the first problem, the serialization. You should be able to tackle that in a similar way as shown above. Just load the data you want to serialize in isolation and disable proxy creation. Or, as suggested by others, serialize view models or anonymous types exactly containing what you need there.
As for the second problem, the validation exception. I can only imagine this to happen if you initialize a students collection by default, empty, Student objects. These are bound to be invalid. If this is not the case, I suggest you ask a new question about this specific problem, showing ample detail about the involved classes and mappings. That shouldn't be dealt with in this question.
Explicitly select what you want to return from the Database.
Use Select new. With the select new clause, you can create new objects of an anonymous type as the result of a query and don't let the reference include in. This syntax allows you to construct anonymous data structures. These are created as they are evaluated (lazily). Like this:
using (var ctx = new myEntities())
{
var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}
And even you don't need to disable lazy loading anymore.
After running query above:
This query currently allocates an anonymous type using select new { }, which requires you to use var. If you want allocate a known type, add it to your select clause:
private IEnumerable<MyClass> AllPersons;//global variable
using (var ctx = new myEntities())
{
AllPersons = ctx.People
.Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}
And:
public class MyClass
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
If entities are auto generated, then copy paste it to own code and remove the relation generated like child collection and Foreign key. Or you don't need all this kind of the functionality might be can user lightweight framework like dapper
In normally your student collection doesn't fill from database. it's fill when you reach to property. In addition if you use ToList() method so Entity Framework read data from data to fill your collection.
Pls check this.
https://msdn.microsoft.com/en-us/data/jj574232.aspx#lazy
https://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx
Is there anyway to don't let the reference include in any time in this situation?
The solution to this seems to be very simple: don't map the association. Remove the Student collection. Not much more I can say about it.
Decorate any properties with [IgnoreDataMember] if you are using 4.5+
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.ignoredatamemberattribute(v=vs.110).aspx
Also sounds like you are trying to do table inheritance which is a different problem with EF
http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application
http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
If I understand you correctly, you're just trying to make sure you only get what you specifically ask for right?
This was mentioned a little above, but to do this correctly you just want to select an anonymous type.
var students = from s in _context.Students
select new{
StudentId,
StudentNo};
Then, when you want to update this collection/object, I'd recommend use GraphDiff. GraphDiff really helps with the problems of disconnected entities and updates (https://github.com/refactorthis/GraphDiff)
So your method would look similar to this:
void UpdateStudent(Student student){
_context.UpdateGraph(student, map =>
map
.AssociatedEntity(c => c.Person));
_context.SaveChanges();
}
This way, you're able to update whatever properties on an object, disconnected or not, and not worry about the association.
This is assuming that you correctly mapped your entities, and honestly, I find it easier to declare the object as a property, not just the ID, and use a mapping file to map it correctly.
So:
class Person{
int Id{get;set;}
string Name{get;set}
}
class Student{
int Id{get;set;}
string StudentNo{get;set;}
Person Person{get;set;}
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
// Primary Key
HasKey(t => t.Id);
// Table & Column Mappings
ToTable("Students");
Property(t => t.Id).HasColumnName("StudentId");
// Relationships
HasRequired(t => t.Person)
.HasForeignKey(d => d.PersonId);
}
}
Hopefully that makes sense. You don't need to create a view model, but you definitely can. This way does make it easier to map disconnected items back to the database though.
I had exact same situation.
All I did to solve it was ask for the Student.ToList() before I asked for Persons.ToList()
I didn't have to disable lazy loading. Just need to load the table that has reference to other table first after that you can load the other table and first table results are already in memory and don't get "fixed" with all the references.
They are automatically linked in the ObjectContext by there EntityKey. Depending on what you want to do with your Persons and Students, you can Detach them from the ObjectContext :
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
foreach(var c in AllPersons)
{
ctx.Detach(c);
}
AllStudents = ctx.Student.ToList();
foreach(var c in AllStudents )
{
ctx.Detach(c);
}
}

Entity Framework setting an entry to modified throws error

While writing an update method for my Entity Framework repository, I include the following code:
public bool UpdateProduct(int id, Models.Product product)
{
Product ctxProduct = GetProductIncludingProductLists(id); //Pulls directly from context
if (ctxProduct != null && product != null)
{
/*Update ctxProduct fields using product*/
_ctx.Entry(ctxProduct).State = EntityState.Modified; //_ctx is my DbContext
return true;
}
return false;
}
But the line of code where I set the entity's status to modified throws the following error:
A first chance exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Attaching an entity of type '..Product' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
I am confused by what this error message is trying to tell me. Since I have pulled this entry out of the context, the only thing its Primary key should be conflicting with is itself. In addition, I know this entry has been assigned an id since I accessed it using its id.
Lastly, the reason I am setting this entry's state to modified is because calling _ctx.saveChanges() is returning 0, indicating to me that the context isn't aware I've changed anything (when I have).
Can anyone explain why this error is being thrown and what I need to do to make the context aware of my changes?
EDIT
GetProductIncludingProductLists(id):
public Product GetProductIncludingProductLists(int id)
{
try
{
return _ctx.Products.Include("ProductLists")
.ToList()
.Select(p => new Product()
{
ProductId = p.ProductId,
CUSIP = p.CUSIP,
SEDOL = p.SEDOL,
BUID = p.BUID,
Key = p.Key,
Track = p.Track,
ProductLists = ((p.ProductLists.Select(l => new ProductList()
{
ProductListId = l.ProductListId,
Name = l.Name,
Products = null
})
.ToList() as List<ProductList>) ?? null)
})
.First(item => item.ProductId == id);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
return null;
}
}
The reason for the crazy select statements is because product and product list are related N:N and not including them would cause an error for 'circular serialization'
looking at this code, I'm afraid there is some lack of knowledge on how EF works.
First, to load an entity this code is overcomplicated and does horrible things:
1: this loads all the products in the DB into memory
return _ctx.Products.Include("ProductLists")
.ToList()
2: this copies the properties of already existing objects in new objects
.Select(p => new Product() { // create new object and copy properties !!
3: this queries individually each of the related lists of each and every product in the DB (and creates a new list, when there is one already available)
p.ProductLists.Select( // query the list of each product !!
4: and all of this just to get a product with the given id!
.First(item => item.ProductId == id);
You can simply do this:
return _ctx.Products
.Include(p => p.ProductLists) // it's much safer a lambda than a magic string
.First(item => item.ProductId == id);
which will load only the required product, with its corresponding product lists. And it will be attached to the context.
Second. If your product is already attached to the context, i.e. you've loaded it using the context, for example as I've just shown, and provided that _ctx has not been disposed, the product is already tracked by the context, and you don't need to care about setting its state. Whenever you make any change to it, the context will automatically change its state so that, when you call SaveChanges the changes will be automatically posted to the DB.
As you can see your code is overcomplicated. Try to make some tutorials to understand how EF works. You'll spare a lot of time. You can use the EF section of MSDN. It has clear documentation and examples.
You are returning a new product that is not being change tracked by Entity Framework:
.Select(p => new Product()
the first ToList() bring all products in the context (are you sure you want that ?).
then you create new one with the select
trying to attach the new one conflict with the loaded one.

Error : An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key

I am new to entity framework.
I have searched on this site and on google before asking this question and everywhere I found different answers. But my problem is not solved, so I am asking this question.
I get the above mentioned error while I try to delete a record.
Here is my code:
using (Lab_Lite_Entities db = new Lab_Lite_Entities())
{
var HaemogramsCorrespondingToPatient = (from h in db.Haemograms
join m in db.MasterPatientHaemograms
on h.HaemogramID equals m.HaemogramID
where m.PatientID == SelectedPatient.PatientID
select h);
foreach (Haemogram haemogram in HaemogramsCorrespondingToPatient)
{
if (db.Entry(haemogram).State == System.Data.EntityState.Detached)
db.Haemograms.Attach(haemogram);
db.Haemograms.Remove(haemogram);
db.Entry(haemogram).State = EntityState.Deleted;
}
var entry = db.Entry(SelectedPatient);
if (entry.State == System.Data.EntityState.Detached)
db.Patients.Attach(SelectedPatient); //I get error here
db.Patients.Remove(SelectedPatient);
db.SaveChanges();
}
Here is the relationship between the tables:
Note: Please note that Cascade delete is on in sql server.
Edit
I have also noticed something strange.
When I create a patient and then try to delete the CurrentPatient object using the above mentioned code, I get the above mentioned error.
But when I create a patient and then restart the program and then I try to delete the CurrentPatient object, then it is deleted without any problems.
I can only imagine that this error occurs if SelectedPatient contains referenced objects of type MasterPatientHaemogram and these again referenced objects of type Haemogram. When you attach SelectedPatient (which is apparently a detached entity) the whole object graph will be attached including Haemogram objects with possibly the same key that you already have loaded in the query for HaemogramsCorrespondingToPatient. This would cause the exception.
Simplest and safest solution is not to try to attach the detached SelectedPatient at all but load a copy from the database and delete this entity instead:
//...
var patient = db.Patients.Find(SelectedPatient.PatientID);
db.Patients.Remove(patient);
db.SaveChanges();
If you dislike to query the database with Find create a stub entity with just the correct key:
//...
var patient = new Patient { PatientID = SelectedPatient.PatientID };
db.Patients.Attach(patient);
db.Patients.Remove(patient);
db.SaveChanges();

How to write Linq method in data tier?

I am thinking about how to use Linq in the classic 3-tier archetecture of .net project. Apprently, Linq to SQL should appear in Data tier. The reason I choose Linq is because it will save me much time on code than using store procedure. I did some search on line about the insert/update/delete method of Linq, but didn't find an appropriate method for record update using entities. Usually, people will do update using this way:
public void UpdateUser(String username, String password, int userId)
{
using (var db = new UserDataContext()){
var user = db.user.Single(p => p.Id = userId);
user.Username = username;
user.Password = password;
db.SubmitChanges();
}
}
Why we don't use entity to pass the record like this:
public void Update(Application info)
{
VettingDataContext dc = new VettingDataContext(_connString);
var query = (from a in dc.Applications
where a.Id==info.Id
select a).First();
query = info;
try{
dc.SubmitChanges();
}
catch(Exception e){
//...
}
}
But unfortunately, the above code is wrong because of "query=info", but if I assign each value from "info" to "query", it works fine. like
query.firstName=info.firstName;
query.lastName=info.lastName;
So if this table have 40 fields, I have to write 40 lines code. Is there any easier way to do the update? Hope I describe this issue clearly.
Adding another answer as a comment was not sufficient to expand on my previous answer.
Lets take a step back and look at what you want to do here from a logical perspective. You want to tell your data access layer how it should update the database, with all the new/changed values it needs to write.
One very common way of doing this is to pass an entity which has those changes (which is what you're doing in your example). This can become tricky, as you have seen, because if you simply overwrite the entity variable with the changed entity, Linq2Sql will lose change tracking... just because the new entity is assigned to the same variable, doesn't mean that Linq2Sql automatically picks up changes from the new object... in fact Linq2Sql has no knowledge of the new object at all...
Example:
// In domain layer:
MyEntity entity = new MyEntity();
entity.PrimaryKey = 10;
entity.Name = "Toby Larone";
entity.Age = 27;
myDataRepository.Update(entity);
// In data layer:
void Update(MyEntity changedEntity)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == changedEntity.PrimaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
entity = changedEntity;
// Linq2Sql does **not** have change tracking of changedEntity - the fact that it has been assigned to the same variable that once stored a tracked entity does not mean that Linq2Sql will magically pick up the changes...
db.SubmitChanges(); // Nothing happens - as far as Linq2Sql is concerned, the entity that was selected in the first query has not been changed (only the variable in this scope has been changed to reference a different entity).
}
}
Now you've already seen that assigning each field to the entity rather than replacing it works as intended - this is because the changes are being made to the original entity, which is still inside the Linq2Sql change tracking system..
One possible solution to this problem would be to write a method that "applies" the changes of another Entity to an existing one, ie:
partial class MyEntity
{
void ApplyChanges(MyEntity changedEntity)
{
this.PrimaryKey = changeEntity.PrimaryKey;
this.Name = changedEntity.Name;
this.Age = changedEntity.Age;
}
}
and then your data access would look like this:
// In data layer:
void Update(MyEntity changedEntity)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == changedEntity.PrimaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
entity.ApplyChanges(changedEntity);
db.SubmitChanges(); // Works OK...
}
}
But im sure you don't like this solution - because all you have done is effectively move the repetitive field assignment out of the repository and into the Entity class itself...
Going back to the logical perspective - all you really need to do is tell the data access repository 2 things - 1) which record you want to update and 2) what the changes are. Sending an entirely new entity which encapsulates those two requirements is not necessary to achieve that goal, in fact I think it's very inefficient.
In the following example, you are sending the data repository only the changes, not an entire entity. Becuase there is no entity, there are no change tracking issues to work around
Example:
// In domain layer:
myDataRepository.Update(10, entity =>
{
entity.Name = "Toby Larone";
entity.Age = 27;
});
// In data layer:
void Update(int primaryKey, Action<MyEntity> callback)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == primaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
// The changes that were sent are being applied directly to the Linq2Sql entity, which is already under change tracking...
callback(entity);
db.SubmitChanges();
}
}
In the previous examples, the field assignments were happening twice - once when you described the changes you wanted to make, and again in the data repository when you needed to apply those changes to a Linq2Sql change tracked entity.
Using the callback, the field assignments only happen once - the description of the change itself is what updates the tracked entity.
I hope I explained this well enough :)
Think about what the data repository actually requires in order to perform the update. It does not require an object that contains those changes, but a description of what changes need to be made. This can be encapsulated easily into a callback delegate...
public void UpdateUser(int userId, Action<User> callback)
{
using (var db = new DataContext())
{
User entity = db.Users.Where(u => u.Id == userId).Single();
callback(entity);
db.SubmitChanges();
}
}
myrepository.UpdateUser(userId, user =>
{
user.Username = username;
user.Password = password;
// etc...
});
query is not the same type as info. They may have the same properties to you, but the code doesn't know that.
Now, if you want to avoid writing a bunch of unnecesary code, you can use a third party library like AutoMapper which can do that for you.

Categories