My original question is here.
Below is my updated code.
Public Function StockTransferItemRemove(removeRequest As StockTransferItemRequest) As StockTransferItemResponse Implements IStockTransferService.StockTransferItemRemove
' create your objects
Dim removeResponse = New StockTransferItemResponse
Dim stockTransfer As New StockTransfer
Dim stockTransferItem As New StockTransferItem
Try
' get the aggregate root
stockTransfer = _stockTransferRepository.FindBy(removeRequest.StockTransferID).FirstOrDefault
stockTransfer.RemoveItem(removeRequest.StockTransferItemView.Id)
_stockTransferRepository.Save(stockTransfer)
Dim count As Integer = _uow.WMSCommit()
If (count > 0) Then
' the object was saved succesfully
removeResponse.Success = True
Else
' the object was not saved successfully
removeResponse.BrokenRules.Add(New BusinessRule(String.Empty, String.Empty, Tags.Messages.Commit_Failed))
End If
Catch ex As Exception
' an unexpected error occured
removeResponse.BrokenRules.Add(New BusinessRule(String.Empty, String.Empty, ex.Message))
End Try
Return removeResponse
End Function
When the unit of work tries to commit it produces the following error message.
The operation failed: The relationship could not be changed because one or more of
the foreign-key properties is non-nullable. When a change is made to a relationship,
the related foreign-key property is set to a null value. If the foreign-key does not
support null values, a new relationship must be defined, the foreign-key property must
be assigned another non-null value, or the unrelated object must be deleted.
I know that when I use StockTransfer.RemoveItem() that it removes the item from the collection but it keeps the record in the database, which is why I am receiving the error.
Is there a way of removing the child object from an aggregate Root and persisting the aggregate root?
Im sorry for the unclear code but im a C# guy so trying to find my way in VB Code. You should use the .Clear() option on the entities link which you want to clear.
Example:
Company <> Employees
Company.Emplyees.Clear() removes all the records in the relation
table.
Thats an issue im having too. I dont know the pure solution, but i always have to delete it in the ef context manualy before saving changes. In your repository method for save You should check for entities which are in the ef context but not in aggregates collection and remove them from the dbset on the context.
Did you find a good solution? I have created a solution using a ParentAttribute and extending the DbContext SaveChanges or ValidateEntity. You can find my solution here.
The answer might be a little late but, this extension method on my data context called DataContext (which inherits from DbContext) worked for me using EF4.3.
public static void Delete<TEntity>(this DataContext context, IEnumerable<TEntity> entities) where TEntity : class, new()
{
foreach (var entity in entities)
{
context.Delete(entity);
}
}
public static void Delete<TEntity>(this DataContext context, TEntity entity) where TEntity : class, new()
{
var obj = context.Entry(entity);
if (obj.State == System.Data.EntityState.Detached)
{
context.Set(typeof(TEntity)).Attach(obj.Entity);
}
context.Set(typeof(TEntity)).Remove(obj.Entity);
}
And the data context class just for completeness.
public class DataContext : DbContext
{
public DbSet<MyPOCO> POCOs { get; set; }
...
}
Related
I have a little project where I'm running MVC3.
I use LINQ to fetch data from the database.
I built my project with the same architectural design as the premade examples that come with MVC3.
In such a project, the application is split up and in this topic I want to focus on the Model.cs files. I have one for each controller at the moment, So as an example, I have a HighscoreController.cs and a HighscoreModels.cs. In the model class I define a Service class that has a reference to a datacontext and some methods that use this datacontext to query the database.
Now i ran into the problem that some of these methods are executing the same queries, and therefore I wanted to make a central point of access to the database so I thought I would implement the Repository Pattern, and so I did.
So instead of having a reference to a datacontext in the Service class I now have a reference to the repository as such:
private IRepository _repository;
public HighscoreService()
: this(new Repository())
{ }
public HighscoreService(IRepository repository)
{
_repository = repository;
}
Now the database calls are handled within the repository and the repository is used from the Service class through the _repository reference.
My repository is built like this:
public class Repository : IRepository
{
private MyDataContext _dataContext;
public Repository()
{
_dataContext = new MyDataContext();
}
public Member MemberByName(string memberName)
{
Member member = CompiledQueries.MemberByName(_dataContext, memberName);
return member;
}
}
The problem I face appears when I try to use DataLoadOptions in combination with this repository pattern.
Because when you use dataloadoptions, you must not have made previous queries on the datacontext before a new dataloadoptions is applied to it. And since my repository reuses the datacontext throughout all methods, this does not work out at all.
I have been trying 2 things, one is recreating the datacontext within every methods, by way of the using statement, to make sure that the datacontext is refreshed every time. But then I get problems when I have fetched the result from the repository back into my model and the scope runs out inside the repository pattern, as the using statement ends, which means that the result cannot be used with e.g. .Count() or .ToList() because the datacontext that supplied me the data has been terminated. I also tried another solution where it uses the same datacontext throughout the whole repository, but makes a new instance in each method that uses dataloadoptions. This felt very dirty ;)
So can anyone give me a suggestion on how to use DataLoadOptions with the repository pattern? and avoid the problems I just described. Or should i not use dataloadoptions and choose another way of doing it? The reason i use DataLoadOptions by the way, is that I want to have some data from related tables.
As a little question on the side: In the code example above you can see that I have placed CompiledQueries within a .cs file of its own. Is this a bad design? Are there any guidelines for where to put compiled queries in an MVC application?
Thanks for reading and hope there are some answers for my questions ;) thanks a lot in advance. If you need more information, just ask.
I am by no means an expert on DataLoadOptions, but from your question and what I've read about it, it seems that you need to use it for eager loading. In reference to this:
"Because when you use dataloadoptions, you must not have made previous queries on the datacontext before a new dataloadoptions is applied to it."
.. to me this sounds like a shortcoming or design flaw with DataLoadOptions (I personally use Entity Framework, not LINQ to SQL). While I do think it's a good idea to have a single data context per HTTP request as offered in the first 2 comments by ngm and CrazyCoderz, I don't think this will solve your problem. If you want to reuse a single data context within a single HTTP request, as soon as you execute the first query, it sounds like you will be unable to set the DataLoadOptions on the data context to a new value.
I saw a presentation at vslive vegas where the presenter offered one of the solutions you mentioned, creating a new data context in each repository method. What you would have to do here is call ToList() or ToArray() before terminating the using statement and returning the method result(s).
As you mentioned, this would make objects not pre-loaded into the enumeration inaccessible after the method returns. However, if you already have the query executed and converted to a List, Collection, Array, or some other concrete IEnumerable, you don't need access to the Count() or ToList() methods any longer. Instead, you can use Array.Length or List.Count or Collection.Count properties.
What else is stopping you from creating a new data context in each repository method? Meaning, what do you need the data context for, after executing the repository method, that you can't get because it's been disposed of?
Reply to comments
For your first query, can you do this?
public Member GetSomeRandomMember()
{
Member[] members = null;
using (var context = new MyDataContext())
{
// execute the query to get the whole table
members = context.Members.ToArray();
}
// do not need to query again
var totalRows = members.Length;
var skipThisMany = PerformRandomNumberComputation(totalRows);
return members.Skip(skipThisMany).FirstOrDefault();
}
Granted that might not be optimal if your Members table has a lot of rows. In that case you would want to execute 2 queries -- 1 to count, and a second to select the row. You could achieve that by opening 2 contexts:
public Member GetSomeRandomMember()
{
using (var context1 = new MyDataContext())
var totalRows = context1.Members.Count();
var skipThisMany = PerformRandomNumberComputation(totalRows);
Member member = null;
using (var context2 = new MyDataContext())
member = context2.Members.Skip(skipThisMany).FirstOrDefault();
return member;
}
For the second part of your comment, I'm not sure I get what you are talking about. The fetching of the data and the making changes to it should all come in a single operation with a single context anyways:
public void SaveMember(int id, string email, bool isSuspended)
{
using (var context = new MyDataContext())
{
var member = context.Members.Single(m => m.Id == id);
member.Email = email;
member.IsSuspended = isSuspended;
context.SaveChanges(); // or whatever the linq to sql equivalent is
}
}
If you want to pass the whole entity to the repository method, you should still query it out so that it is attached to the correct context:
public void SaveMember(Member member)
{
var memberDto = member;
using (var context = new MyDataContext())
{
member = context.Members.Single(m => m.Id == memberDto.Id);
member.Email = memberDto.Email;
member.IsSuspended = memberDto.IsSuspended;
context.SaveChanges(); // or whatever the linq to sql equivalent is
}
}
I'm using EF4 POCOs and UnitOfWork/repository patterns with MVC 3. I'm trying to understand how I would modify a new record that is to be inserted.
My service method to insert/update looks something like this (the repository is injected in the service constructor via IoC):
public void UpdateData(Guid id, int newValue)
{
MyPoco poco = _repository.FirstOrDefault(p => p.Id = id);
if (poco == null)
{
poco = new Poco
{
//set properties
};
_repository.Add(poco);
}
poco.SomeFieldToUpdate = newValue;
}
And my changes get persisted via my UnitOfWork on a UseUnitOfWorkAttribute action filter on my controller:
void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
{
var unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
unitOfWork.Commit();
}
Of course, this works fine if this is ever hit just once, for existing or new data. And it works fine on multiple passes if it already exists.
But if the Guid value doesn't exist in the table, then it tries to do multiple inserts if this is called multiple times.
So that's my dilemma. I understand why this doesn't work, I'm just not sure the proper way to fix it. Basically, I need to somehow get a reference to the existing POCO in the UnitOfWork, and somehow update it. But the UnitOfWork is not available in my service (by design) -- and I'm not even sure I know how to pull an entity out of the UoW and update it anyway.
Am I going about this wrong or am I overlooking something simple here? Or do I have a fundamental flaw in how I've designed this? I have a feeling I may be making this harder than it should be.
Thanks in advance.
The reason why this happens is because your entity is not saved yet and you execute query to get it. Query will not find it in database and correctly return null.
You should not need to use repository / unit of work / ObjectContex as internal storage of not saved entities among service calls. If you need it you should check your application design and refactor it because something is probably wrong.
Anyway you can get not saved entity from context but it is not very nice code. You will need special method on your repository to get entity by id. You will use it instead of calling FirstOrDefault. Something like:
public MyPoco GetById(Guid id)
{
MyPoco enity = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e => e.Entity != null && e.Entity.GetType() == typeof(MyPoco)))
.Select(e => (MyPoco)e.Entity)
.Where(p => p.Id == id)
.SingleOrDefault();
if (entity == null)
{
entity = context.MyPocos.FirstOrDefault(p => p.Id == id);
}
}
Do you set the id you pass into UpdateData as the key on the new Poco object, like so:
poco = new Poco
{
Id = id;
//set properties
};
If yes, you could query for the object not with FirstOrDefault but by using the TryGetObjectByKey in a repository method:
public Poco GetPocoByKey(Guid id)
{
EntityKey key = new EntityKey("MyEntities.Pocos", "Id", id);
object pocoObject;
if (context.TryGetObjectByKey(key, out pocoObject))
return (Poco)pocoObject;
return null;
}
The advantage is that TryGetObjectByKey looks at first into the ObjectContext if it can find an object with the specified key. Only if not, then the database will be queried. Since you add the new Poco to the context the first time it isn't found, TryGetObjectByKey should find it in the context when you search for the object with the same key a second time, even if it has not been saved to the database yet.
Edit: This solution doesn't work!
Because TryGetObjectByKey does not find the key for objects which are in added state in the ObjectContext, even not if the key is not a DB generated key and supplied by the application (see comments below).
All entity created by EF is partial class. so it is extendable. Suppose I have entity Person like
partial class Person{FirstName, LastName, .....}
Then I want to add a compute property Name like:
partial class Person{
[DataMember]
public string Name
{
get { return String.Format("{0} {1}", this.FirstName, this.LastName); }
}
partial void OnFirstNameChanged()
{
//.....
this.ReportPropertyChanged("Name");
}
partial void OnLastNameChanged()
{
//.....
this.ReportPropertyChanged("Name");
}
//....
}
Then for data upate operation I got following error:
The property 'Name' does not have a valid entity mapping on the entity object. For more information, see the Entity Framework documentation.
How to fix this solution?
I've just had the same error.
Do not use "ReportPropertyChanged()" but "OnPropertyChanged()" instead. There you go.
ReportPropertyChanged() only works for real entity objects (like FirstName and LastName that are e.g. real database fields), but not those computed ones (like Name, which only exists in your partial class).
The problem is with those ReportPropertyChanged("Name"), you are reporting to ObjectStateManager that the "Name" property has been changed, while this property does not exists in your model metadata (it has just been declared in your partial class, ObjectContext and ObjectStateManager do not know anything about this property).
If you add those OnLastNameChanged and OnFirstNameChanged partial methods, just get rid of them, you don't need them.
I ran into an interesting problem (hopefully, interesting not just for me :)
I am running Entity Framework 1 (.NET 3.5) and ASP.NET MVC 2. I have a Customer class that has many-to-one relationship with Country class (in other words, Country is a lookup table for customers - I described more in this post: Explicit casting doesn't work in default model binding )
I got TypeConverter to work; so I am getting a perfect object into controller's Post method. Create works fine; however, in Edit I am getting the following error when I call ApplyPropertyChanges:
The existing object in the ObjectContext is in the Added state. Changes can only be applied when the existing object is in an unchanged or modified state.
The controller code is fairly trivial:
public ActionResult Edit(Customer customerToEdit) {
if (ModelState.IsValid) {
Customer cust = (Customer)context.GetObjectByKey(
new EntityKey("BAEntities.Customers", "CustomerID", customerToEdit.CustomerID));
context.ApplyPropertyChanges(cust.EntityKey.EntitySetName, customerToEdit);
context.SaveChanges();
}
return View(...);
}
If I remove country from the form, it works fine; and if I assign dropdown value to EntityReference "manually" - it works as well. UPDATE: it looks that if I don't have lookup field, customerToEdit is in Unchanged state (as I expect); however, if the lookup is there and TypeConverter is invoked - the object is now in Added state. So, the ultimate question is, how can I prevent the object from becoming Added while still using TypeConverter...
TypeConverter code is also fairly simple, but I've never used TypeConverter before, so I may be missing something here:
public override object ConvertFrom(ITypeDescriptorContext typeContext, CultureInfo culture, object value) {
if (value is string) {
int countryID = Int16.Parse((string)value);
Country country = (Country)context.GetObjectByKey(
new EntityKey("BAEntities.Countries", "CountryID", countryID));
return country;
}
return base.ConvertFrom(typeContext, culture, value);
}
UPDATE 2: FWIW, I can see that the state gets changed when DefaultModelBinder::SetProperty is calling propertyDescriptor.SetValue(bindingContext.Model, value); Since PropertyDescriptor is not part of MVC, I can't debug any further. Anybody knows why PropertyDescriptor marks Model as "added" and what can I do about it?
Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.
Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.
When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.
The code to save looks something like:
m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();
How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.
I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.
Descend into SQL
This approach ditches LINQ in favour of straight SQL:
public override void SaveMyObject(MyObject o)
{
// Submit
m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}
I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.
Use a Custom Model Binder
This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.
public class MyObjectBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
return a;
}
}
The repository then creates the object and associates it with the data context:
public Object GetMyObjectForUpdate(string id)
{
MyObject o=new MyObject();
o.ID=id;
m_DataContext.Articles.Attach(o);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
return o;
}
The action handler needs to be attributed to use the model binder...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
if (!ModelState.IsValid)
return View("EditMyObject", a);
Repository.SaveMyObject(a);
return RedirectToAction("Index");
}
and finally SaveMyObject simply calls datacontext.SubmitChanges().
For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).
Overall, this approach works but is messy.
Use Two Entity Objects
This approach uses two entity objects, one for the model binding and one LINQ:
public override void SaveMyObject(MyObject o)
{
// Create a second object for use with linq and attach to the data context
MyObject o2 = new MyObject();
o2.ID = o.ID;
m_DataContext.Articles.Attach(o2);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
// Apply fields edited by the form
o2.A = o.A;
// Submit
m_DataContext.SubmitChanges();
}
This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires
the Update Check property to be set to Never in the dbml file.
You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.