I have a PurchaseOrder aggregate root which has Two methods FinalizeOrder and CancellOrder, they both record events: OrderFinaziled and OrderCancelled. I am stuck on modeling order repository, can i use those events inside repository pattern to update entity in database? i don't wont after each change to save whole aggregate root, i want to save only the field that was changed, i am using SqlClient, no ORMs.
my Aggregate root base class:
public class AggregateRootBase<TID> : EntityBase<TID>
{
public AggregateRootBase(TID id) : base(id)
{
}
private readonly List<IDomainEvent> recordedEvents = new List<IDomainEvent>();
public IEnumerable<IDomainEvent> GetEvents()
{
return recordedEvents;
}
public void MarkEventsAsProcessed()
{
recordedEvents.Clear();
}
protected void RecordEvent(IDomainEvent #event)
{
recordedEvents.Add(#event);
}
}
PurchaseOrder class (skipped most properties):
public class PurchaseOrder : AggregateRootBase<int>
{
public PurchaseOrder(int id) : base(id)
{
IsFinalized = false;
IsCancelled = false;
}
public bool IsFinalized { get; set; }
public bool IsCancelled { get; set; }
public void FinalizeOrder()
{
IsFinalized = true;
RecordEvent(new OrderFinalized(Id,IsFinalized));
}
public void CancellOrder()
{
IsCancelled = true;
RecordEvent(new OrderCancelled(Id,IsCancelled));
}
}
and repository:
public class PurchaseOrderRepository
{
void Save(PurchaseOrder purchaseOrder)
{
var events = purchaseOrder.GetEvents();
foreach (var evt in events)
{
if(evt.GetType() == typeof(OrderFinalized))
// use event args and update field using SqlCommand
else if (evt.GetType() == typeof(OrderCancelled))
// use event args and Update field Using SqlCommand
}
}
}
i also have an EventDispatcher which dispatches events (Email Notification) after AggregateRoot successful persistence.
If you want to use domain events to save your changes, you are talking about projecting events to your aggregate state. There are some tools that can help with that. What you are trying to do resembles the pattern matching, a usual feature of most functional languages. To make life easier you might want to check something like Projac. We use it for projections and it works very nicely. It also has SQL Server specific implementation.
One example that you can find there:
public class PortfolioProjection : SqlProjection
{
public PortfolioProjection()
{
When<PortfolioAdded>(#event =>
TSql.NonQueryStatement(
"INSERT INTO [Portfolio] (Id, Name) VALUES (#P1, #P2)",
new { P1 = TSql.Int(#event.Id), P2 = TSql.NVarChar(#event.Name, 40) }
));
When<PortfolioRemoved>(#event =>
TSql.NonQueryStatement(
"DELETE FROM [Portfolio] WHERE Id = #P1",
new { P1 = TSql.Int(#event.Id) }
));
When<PortfolioRenamed>(#event =>
TSql.NonQueryStatement(
"UPDATE [Portfolio] SET Name = #P2 WHERE Id = #P1",
new { P1 = TSql.Int(#event.Id), P2 = TSql.NVarChar(#event.Name, 40) }
));
}
}
Then you can initialise projector instance:
_projector = new SqlProjector(
Resolve.WhenEqualToHandlerMessageType(new PortfolioProjection()),
new TransactionalSqlCommandExecutor(
new ConnectionStringSettings(
"projac",
#"Data Source=(localdb)\ProjectsV12;Initial Catalog=ProjacUsage;Integrated Security=SSPI;",
"System.Data.SqlClient"),
IsolationLevel.ReadCommitted));
And then project events:
void Save(PurchaseOrder purchaseOrder) =>
_projector.Project(purchaseOrder.GetEvents());
You might also check the event sourcing pattern although it adds quite lot of complexity.
Related
I try example in this topic
https://github.com/DevExpress-Examples/XPO_how-to-create-an-xpclassinfo-descendant-to-dynamically-build-a-persistent-class-structure-e1729
But It only works for single primary key tables. So i searched more Found this:
https://github.com/DevExpress-Examples/XPO_how-to-create-persistent-classes-mapped-to-tables-with-a-composite-primary-key-at-runtime-e4606
Buts there's big different and I think its not satisfy because it speaking about composite key( one Key which have many columns)
So all I need please an example of creating XPO dynamically from SQL -Server Table:
My Table Schema as following
The XPOCollectionSource then binding to grid in server-mode… Thats all I need.
Code I Use
XPServerCollectionSource GetServerModeSourceForTable(IDbConnection connection, string tableName) {
XPDictionary dict = new ReflectionDictionary();
XPClassInfo classInfo = dict.CreateClass(dict.QueryClassInfo(typeof(LiteDataObject)),
tableName);
DBTable[] tables = ((ConnectionProviderSql)XpoDefault.GetConnectionProvider(connection,
AutoCreateOption.None)).GetStorageTables(tableName);
foreach (DBColumn col in tables[0].Columns) {
XPMemberInfo member = classInfo.CreateMember(col.Name, DBColumn.GetType(
col.ColumnType));
if (tables[0].PrimaryKey.Columns.Contains(col.Name))
member.AddAttribute(new KeyAttribute());
}
return new XPServerCollectionSource(new Session(XpoDefault.GetDataLayer(
connection, dict, AutoCreateOption.None)), classInfo);
}
At a glance. How to use XPServerCollectionSource with Dynamically created XPO object. with two primary keys.
https://github.com/DevExpress-Examples/XPO_how-to-create-persistent-classes-mapped-to-tables-with-a-composite-primary-key-at-runtime-e4606/blob/19.2.7%2B/CS/XpoConsoleApplication/XPComplexCustomMemberInfo.cs
This is a class must used. who need more details about that. I can help him for free.
Implementation can be like:
[NonPersistent]
public class XPDynamicObject : XPLiteObject
{
public XPDynamicObject(Session session) : base(session) {}
public XPDynamicObject(Session session, XPClassInfo classInfo) : base(session, classInfo) { }
}
Button_Click or any event :
XPDynamicObject.AutoSaveOnEndEdit = false;
ReflectionDictionary dic = new ReflectionDictionary();
var classInfo = dic.CreateClass(dic.GetClassInfo(typeof(XPDynamicObject)), "general.users");
// WE MUST get schema from database .... via ConnectionProviderSql this is only way
var provider = XpoDefault.GetConnectionProvider(MSSqlConnectionProvider.GetConnectionString("(local)", "testdb"), AutoCreateOption.None) as ConnectionProviderSql;
// Composite Key - this is only way to add composite key dynamically
XPComplexCustomMemberInfo user_key = new
XPComplexCustomMemberInfo(classInfo, "user_key", typeof(object), new KeyAttribute(), new PersistentAttribute(), new BrowsableAttribute(false));
user_key.AddSubMember("user_brn", typeof(int), new PersistentAttribute("user_brn"));
user_key.AddSubMember("user_num", typeof(int), new PersistentAttribute("user_num"));
var user_name = classInfo.CreateMember("user_name", typeof(string));
user_name.AddAttribute(new PersistentAttribute("user_name"));
dal = new SimpleDataLayer(dic, provider);
XPServerCollectionSource xpServerCollectionSource = new XPServerCollectionSource(session, classInfo); // XPServerCollectionSource Only Editable server-mode datasource via ALlowNew, AllowEdit, AllowRemove properties
GridControl.DataSource = xpServerCollectionSource ;
Full Cheat Sheet (Design-Time and Run-Time XPOs)
For Searching for Object by Key or Condition use following:
Edit: 2021-05-24
For those who asks about Associations with composite keys. Its really nightmare. By default Xpo doesn't provide that. instead you can use a Session.GetObjectByKey() in child-class and a new XPCollection in parent class. But actually its something little slowly that native way with an artificial key.
At a glance: Use Artifcial / Single Key Column for Better any ORM
compatibility. Otherwise It will be nightmare in database later. If
you for example support another or replace current ORM.
Here's a code for associations with composite keys. (Note that you need to convert it dynamically like above pictures. You maybe need to create custom XPMemberInfo and override GetValue() to return Session.GetObjectByKey() and create new XPCollection. (really bad stuff to do and worst ever and non-supported ever by Xpo. So Composite-Keys technically are bad way except if you need create Single Table bind it to grid. EX:- SalesOrderDetail table which doesn't have more child tables)
[Persistent("users")]
public class user : XPLiteObject
{
public user(Session session) : base(session) { }
public user(Session session, XPClassInfo classInfo) : base(session, classInfo) { }
[Key, Persistent]
public user_key user_key { get; set; }
[Persistent("user_name")]
public string user_name { get; set; }
private XPCollection<Trans> _trans;
public XPCollection<Trans> trans
{
get
{
if (_trans == null)
_trans = new XPCollection<Trans>(Session, CriteriaOperator.Parse($"user_brn = {user_key.user_brn} AND user_num = {user_key.user_num}"));
return _trans;
}
}
}
public class Trans : XPLiteObject
{
public Trans(Session session) : base(session) { }
public Trans(Session session, XPClassInfo classInfo) : base(session, classInfo) { }
[Key,Persistent("TransID")]
public int TransID { get; set; }
public int user_brn { get; set; }
public int user_num { get; set; }
public user user
{
get
{
var key = new user_key();
key.user_brn = 1;
key.user_num = 3;
var obj = Session.GetObjectByKey<user>(key, true);
return obj;
}
}
}
// Composite-Key
public struct user_key
{
[Persistent("user_brn")]
public int user_brn { get; set; }
[Persistent("user_num")]
public int user_num { get; set; }
}
A Dynamic run-time version from above model:
class XPCompositeAssociationMemberInfo : XPCustomMemberInfo
{
public XPCompositeAssociationMemberInfo(XPClassInfo owner, string propertyName, Type propertyType, XPClassInfo referenceType, bool nonPersistent, bool nonPublic) : base(owner, propertyName, propertyType, referenceType, nonPersistent, nonPublic)
{
}
public override object GetValue(object theObject)
{
XPDynamicObject val = theObject as XPDynamicObject;
if(val != null)
{
var user_num = val.GetMemberValue("user_num");
IdList vals = new IdList();
vals.Add(1);
vals.Add(user_num);
return val.Session.GetObjectByKey(ReferenceType, vals);
}
return val;
}
}
XPDynamicObject.AutoSaveOnEndEdit = false;
ReflectionDictionary dic = new ReflectionDictionary();
var userClassInfo = dic.CreateClass(dic.GetClassInfo(typeof(XPDynamicObject)), "users");
// WE MUST get schema from database .... via ConnectionProviderSql this is only way
var provider = XpoDefault.GetConnectionProvider(MSSqlConnectionProvider.GetConnectionString("(local)", "testdb"), AutoCreateOption.None) as ConnectionProviderSql;
//DBTable Table = provider.GetStorageTables("users")[0];
// Composite Key - this is only way to add composite key dynamically
XPComplexCustomMemberInfo user_key = new XPComplexCustomMemberInfo(userClassInfo, "user_key", typeof(object), new KeyAttribute(), new PersistentAttribute(), new BrowsableAttribute(false));
user_key.AddSubMember("user_brn", typeof(int), new PersistentAttribute("user_brn"));
user_key.AddSubMember("user_num", typeof(int), new PersistentAttribute("user_num"));
var user_name = userClassInfo.CreateMember("user_name", typeof(string));
user_name.AddAttribute(new PersistentAttribute("user_name"));
var transClassInfo = dic.CreateClass(dic.GetClassInfo(typeof(XPDynamicObject)), "Trans");
var TransID = transClassInfo.CreateMember("TransID", typeof(int), new KeyAttribute());
var user_brn = transClassInfo.CreateMember("user_brn", typeof(int));
var user_num = transClassInfo.CreateMember("user_num", typeof(int));
XPCompositeAssociationMemberInfo userMI = new XPCompositeAssociationMemberInfo(transClassInfo, "user", typeof(object), userClassInfo, true, false);
dal = new SimpleDataLayer(dic, provider);
Session session = new Session(dal);
XPServerCollectionSource xpServerCollectionSource = new XPServerCollectionSource(session, transClassInfo); // XPServerCollectionSource Only Editable server-mode datasource via ALlowNew, AllowEdit, AllowRemove properties
xpServerCollectionSource.DisplayableProperties = "TransID;user.user_key.user_num;user.user_name";
Finally, IMPORTANT !
Its really very very hard to achieve Composite-Keys cases at runtime
in any Xpo. Instead replace with Single Column Key(Identity or
Artificial one. GUID one etc.)
I don't know if this is what you want, but I'll share my code using XPInstantFeedbackSource.
You can get more information at How To Implement The Instant Feedback Mode Xpo
XPLiteObject LogXPOModel
[Persistent("log.t_log")]
public class LogXPOModel : XPLiteObject
{
[Key, DevExpress.Xpo.DisplayName("id")]
public long id { get; set; }
[Key, DevExpress.Xpo.DisplayName("recv_time")]
public long recv_time
...
}
Window MainWindow
public class MainWindow : Window
{
public XPInstantFeedbackSource SqliteXPInstantFeedbackSource
public MainWindow() {
...
SqliteXPInstantFeedbackSource = new XPInstantFeedbackSource();
SqliteXPInstantFeedbackSource.ObjectType = typeof(LogXPOModel);
SqliteXPInstantFeedbackSource.ResolveSession += SqliteXPInstantFeedbackSource_ResolveSession;
SqliteXPInstantFeedbackSource.DismissSession += SqliteXPInstantFeedbackSource_DismissSession;
View.Grid.ItemsSource = SqliteXPInstantFeedbackSource;
}
...
}
Like that — my table has two keys; and, it does not incur any problems.
I'm going to chunk this down to as simple a case as I can, but this happens for everything.
I'm basing most of my data model POCO objects on a BaseDataObject defined as follows:
public class BaseDataObject
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
My code-first data model has a Client object:
public class Client : BaseDataObject
{
public string Name { get; set; }
public virtual Category Category { get; set; }
public virtual Category Subcategory { get; set; }
}
The Category object is pretty simple:
public class Category : BaseDataObject
{
public string Name { get; set; }
}
The required Id property exists in the inherited BaseDataObject.
To add entities, I'm using the following repo:
public class DataRepository<TModel, TContext>
where TModel : BaseDataObject
where TContext : DbContext
{
public int AddItem(T item)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
// These are important as well.
public List<T> ListItems(int pageNumber = 0)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
// Deleted property is also included in BaseDataObject.
return db.Set<T>().Where(x => !x.Deleted).OrderBy(x => x.Id).Skip(10 * pageNumber).ToList();
}
public T GetSingleItem(int id)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
return db.Set<T>().SingleOrDefault(x => x.Id == id && !x.Deleted);
}
}
}
This adds a new client perfectly fine, but there's something weird about my data model here that's causing Entity Framework to also add 2 new Categories every time I add a client based on which categories I'm selecting on my form.
Here's my form's code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
BindDropDownList<Category>(CategoryList);
BindDropDownList<Category>(SubcategoryList);
}
// Error handling things
}
}
private void BindDropDownList<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
control.DataSource = repo.ListItems();
control.DataTextField = "Name";
control.DataValueField = "Id";
control.DataBind();
control.Items.Insert(0, new ListItem("-- Please select --", "0"));
}
private TModel GetDropDownListSelection<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
int.TryParse(control.SelectedItem.Value, out int selectedItemId);
return repo.GetSingleItem(selectedItemId);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
Category = selectedCategory,
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
Unless there's something wrong with the way I'm creating the relationship here (using the virtual keyword or something maybe) then I can't see any reason why this would add new Categories to the database as duplicates of existing ones based on the selections I make in the drop down lists.
Why is this happening? What have I got wrong here?
The DbSet<T>.Add method cascades recursively to navigation properties which are not currently tracked by the context and marks them as Added. So when you do
db.Set<T>().Add(item);
it actually marks both Client class referenced Category entities as Added, hence SaveChanges inserts two new duplicate Category records.
The usual solution is to tell EF that entities are existing by attaching them to the context in advance. For instance, if you replace repo.AddItem(client); with
using (var db = new ApplicationDbContext())
{
if (client.Category != null) db.Set<Category>().Attach(client.Category);
if (client.Subcategory != null) db.Set<Category>().Attach(client.Subcategory);
db.Set<Client>().Add(item);
db.SaveChanges();
}
everything will be fine.
The problem is that you use generic repository implementation which does not provide you the necessary control. But that's your design decision issue, not EF. The above is EF intended way to handle such operation. How you can fit it into your design is up to you (I personally would eliminate the generic repository anti-pattern and use directly the db context).
It is really hard to judge from your listing because no FK mappings are included nor the base model details are provided.
However, it would appear that the Category that you assigned to client does not have PK set, and (most likely) only has the Name set, and you have no unique IX on that.
So EF has no reasonable way to work out that this is the right category.
One way to sort it is
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext>();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
// either
Category = new DataRepository<Category , ApplicationDbContext>().GetSingleItem(selectedCategory.id),
// or, easier (assuming you have FK properties defined on the model)
CategoryId = selectedCategory.Id,
// repeat as needed
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
I have two classes: User and Site. User and Site have a many-to-many relationship. A Site object S has a property indicating whether or not a User U should be validated before U is added to S. To validate a User, the application retrieves validation rules from the Site and checks to see that the User's properties' values match the validation rules' values. If they all match/pass, the User is "valid" to be added to that Site.
How would you structure this relationship? My first thought is to create an intermediate class (i.e. Mediator design pattern?) that has a field of type IEnumerable<User> and Site so I can encapsulate the Site validation setting retrieval. Also, I'm thinking of adding an "IsValid" property to the User class. Is that all I need? I want to make sure things are not tightly coupled.
Thoughts?
Here's similar code I wrote up:
public class User
{
public int _userId;
public string _fname;
public string _lname;
public User(string connectionString, int id)
{
using (var dc = new DataContext(connectionString))
{
var user = dc.Users.SingleOrDefault(u => u.ID == id);
_userId = user.ID;
_fname = user.FName;
_lname = user.LName;
}
}
public bool IsValidUser(int siteId)
{
bool isValid = true;
// logic here probably won't change
var conditions = Site.GetConditions(_userId);
// e.g. checks _fname, _lname
return Site.UserMeetsConditions(_id, conditions);
}
}
public class Site
{
public int _siteId;
public List<Setting> _siteSettings;
public Site(string connectionString, int id)
{
using (var dc = new DataContext(connectionString))
{
var site = dc.Sites.SingleOrDefault(u => u.ID == id);
_siteId = site.ID;
}
}
public void SetSiteSettings(string connectionString)
{
using (var dc = new DataContext(connectionString))
{
_siteSettings = dc.SiteSettings.Select(s => s).ToList();
}
}
public bool SiteRequiresValidityCheck()
{
return _siteSettings.Any(s => s.SettingID = 100 && s.Value == true);
}
}
public Validator
{
public List<User> users;
public bool _requiresValidityCheck;
public bool UsersAreValid(int siteId)
{
bool usersAreValid = true;
if (_requiresValidityCheck)
{
foreach (var user in users)
{
if (!user.IsValid)
{
usersAreValid = false;
break;
}
}
}
return usersAreValid;
}
}
static void Main()
{
string cs = myconnectionstring;
var user1 = new User(cs, 1);
var user2 = new User(cs, 2);
List<User> users = new List<User>() { user1, user2 };
var site = new Site(cs, 10);
site.SetSiteSettings(cs);
var validator = new Validator();
validator.Users = users;
validator._requiresValidityCheck = site.SiteRequiresValidityCheck();
bool usersAreValid = validator.UsersAreValid(site._siteId);
}
If this is a POCO class or a MODEL you could use DataAnnotations on your class attributes.
Like user
[Required]
etc attributes for each attribute.
Data annotations also has support for cross attribute checking that you can implement those too.
take a look at them here:
https://msdn.microsoft.com/en-us/library/dd901590%28VS.95%29.aspx
Frustrating, this. Here's a pair of related objects, as generated by database-first Entity Framework:
public partial class DevelopmentType
{
public DevelopmentType()
{
this.DefaultCharges = new HashSet<DefaultCharge>();
}
public System.Guid RowId { get; set; }
public string Type { get; set; }
public virtual ICollection<DefaultCharge> DefaultCharges { get; set; }
}
public partial class DefaultCharge
{
public System.Guid RowId { get; set; }
public decimal ChargeableRate { get; set; }
public Nullable<System.Guid> DevelopmentType_RowId { get; set; }
public virtual DevelopmentType DevelopmentType { get; set; }
}
Here's the code that I'm calling to save a DevelopmentType - it involves automapper since we differentiate entity objects from DTOs:
public void SaveDevelopmentType(DevelopmentType_dto dt)
{
Entities.DevelopmentType mappedDevType = Mapper.Map<DevelopmentType_dto, Entities.DevelopmentType>(dt);
_Context.Entry(mappedDevType).State = System.Data.EntityState.Modified;
_Context.DevelopmentTypes.Attach(mappedDevType);
_Context.SaveChanges();
}
In my user interface, the most common operation will be for a user to look at a list of DevelopmentTypes and update their DefaultCharge. So when I test this using the above code, it runs without error, but nothing actually changes.
If I pause in the debugger it's clear that the changed DefaultCharge is being passed into the function, and that it's attached to the DevelopmentType to be saved.
Stepping through it, if I change the value manually inside visual studio, it does save the updated value. Which is just even more confusing.
Monitoring the database with SQL Server Profiler reveals that update commands are issued only for the parent object and not for any attached objects.
I have other similar code elsewhere that functions as expected. What am I doing wrong here?
EDIT:
I have discovered that if you do this prior to the call to SaveDevelopmentType:
using (TransactionScope scope = new TransactionScope())
{
dt.Type = "Test1";
dt.DefaultCharges.First().ChargeableRate = 99;
_CILRepository.SaveDevelopmentType(dt);
scope.Complete();
}
The change to Type saves, but the change to ChargeableRate does not. I don't think it helps, massively, but thought I'd add it.
The problem is, that EF is not aware of the changed DefaultCharges.
By setting the State of the DevelopmentType to EntityState.Modified, EF only knows that the object DevelopmentType has been changed. However, this means that EF will only update DevelopmentType but not it's navigation properties.
A workaround - which isn't best practice - would be to iterate over all DefaultCharge of the current DevelopmentType and set the entity state to EntityState.Modified.
Additionally I would recommend to attach the entity to the context first, and change the state afterwards.
EDIT after comment
As you are using DTOs I suppose you are transfering these objects either through different layers or different machines.
In this case I would recommend to use self tracking entities, because it is not possible to share one context. These entities additionally holds their current state (ie. new, updated, deleted etc). There are many tutorials on the net about self tracking entities.
e.g. MSDN - Working with Self-Tracking Entities
As far as I know EF can save child entities only if the parent object was retrieved with the same Context that is trying to save it. That is attaching an object that was retrieved by one context to another context, will allow you to save changes to parent objects but not children. This was the result of a on old search based on which we switched to NHibernate. If memory serves correctly I was able to find a link where EF team member(s) confirmed this and that there WAS no plan to change this behavior. Unfortunately all links related to that search have been erased from my PC since.
As I am not aware of how you are retrieving the objects in your case, I am not sure this is relevant to your case, but put it out there just in case it helps.
Here is a link on attaching detached objects to a context.
http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and
Context.Entry() already "Attaches" the Entity internally in order to have the context change its EntityState.
By calling Attach() you're changing the EntityState back to Unchanged. Try to comment out this line.
The Graphdiff library was a great help for me to handle all of these complexities.
You only need to set up the navigation properties that you wish to insert/update/delete (using fluent syntax) and Graphdiff will take care of it
Note: It seems to be that the project is not updated anymore but i'm using it since more than a year and is quite stable
This is not a workaround for every case, but I did discover that you can get around this by updating foreign keys on an object instead of updating navigation property objects.
For example... instead of:
myObject.myProperty = anotherPropertyObject;
Try this:
myObject.myPropertyID = anotherPropertyObject.ID;
Make sure the object is flagged as modified in EF's mind (as mentioned in other posts) and then call your save method.
Worked for me at least! It'll be a no-go when working with nested properties, but perhaps you can break your contexts up into smaller chunks and work over objects in multiple parts to avoid context bloat.
Good luck! :)
If I understand the question correctly, you have problem updating child fields. I had problems with child collection fields. I tried this and it worked for me.
You should update all child collections after attaching the object to the database context change the modified state of the parent object and save changes to the context.
Database.Products.Attach(argProduct);
argProduct.Categories = Database.Categories.Where(x => ListCategories.Contains(x.CategoryId)).ToList();
Database.Entry(argProduct).State = EntityState.Modified;
Database.SaveChanges();
I created a helper method to solve this problem.
Consider this:
public abstract class BaseEntity
{
/// <summary>
/// The unique identifier for this BaseEntity.
/// </summary>
[Key]
public Guid Id { get; set; }
}
public class BaseEntityComparer : IEqualityComparer<BaseEntity>
{
public bool Equals(BaseEntity left, BaseEntity right)
{
if (ReferenceEquals(null, right)) { return false; }
return ReferenceEquals(left, right) || left.Id.Equals(right.Id);
}
public int GetHashCode(BaseEntity obj)
{
return obj.Id.GetHashCode();
}
}
public class Event : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public HashSet<Manager> Managers { get; set; }
}
public class Manager : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public Event Event{ get; set; }
}
DbContext with the helper method:
public class MyDataContext : DbContext
{
public MyDataContext() : base("ConnectionName") { }
//Tables
public DbSet<Event> Events { get; set; }
public DbSet<Manager> Managers { get; set; }
public async Task AddOrUpdate<T>(T entity, params string[] ignoreProperties) where T : BaseEntity
{
if (entity == null || Entry(entity).State == EntityState.Added || Entry(entity).State == EntityState.Modified) { return; }
var state = await Set<T>().AnyAsync(x => x.Id == entity.Id) ? EntityState.Modified : EntityState.Added;
Entry(entity).State = state;
var type = typeof(T);
RelationshipManager relationship;
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
if (stateManager.TryGetRelationshipManager(entity, out relationship))
{
foreach (var end in relationship.GetAllRelatedEnds())
{
var isForeignKey = end.GetType().GetProperty("IsForeignKey", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end) as bool?;
var navigationProperty = end.GetType().GetProperty("NavigationProperty", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end);
var propertyName = navigationProperty?.GetType().GetProperty("Identity", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(navigationProperty) as string;
if (string.IsNullOrWhiteSpace(propertyName) || ignoreProperties.Contains(propertyName)) { continue; }
var property = type.GetProperty(propertyName);
if (property == null) { continue; }
if (end is IEnumerable) { await UpdateChildrenInternal(entity, property, isForeignKey == true); }
else { await AddOrUpdateInternal(entity, property, ignoreProperties); }
}
}
if (state == EntityState.Modified)
{
Entry(entity).OriginalValues.SetValues(await Entry(entity).GetDatabaseValuesAsync());
Entry(entity).State = GetChangedProperties(Entry(entity)).Any() ? state : EntityState.Unchanged;
}
}
private async Task AddOrUpdateInternal<T>(T entity, PropertyInfo property, params string[] ignoreProperties)
{
var method = typeof(EasementDataContext).GetMethod("AddOrUpdate");
var generic = method.MakeGenericMethod(property.PropertyType);
await (Task)generic.Invoke(this, new[] { property.GetValue(entity), ignoreProperties });
}
private async Task UpdateChildrenInternal<T>(T entity, PropertyInfo property, bool isForeignKey)
{
var type = typeof(T);
var method = isForeignKey ? typeof(EasementDataContext).GetMethod("UpdateForeignChildren") : typeof(EasementDataContext).GetMethod("UpdateChildren");
var objType = property.PropertyType.GetGenericArguments()[0];
var enumerable = typeof(IEnumerable<>).MakeGenericType(objType);
var param = Expression.Parameter(type, "x");
var body = Expression.Property(param, property);
var lambda = Expression.Lambda(Expression.Convert(body, enumerable), property.Name, new[] { param });
var generic = method.MakeGenericMethod(type, objType);
await (Task)generic.Invoke(this, new object[] { entity, lambda, null });
}
public async Task UpdateForeignChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var children = (childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>()).ToList();
foreach (var child in children) { await AddOrUpdate(child); }
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
foreach (var child in existingChildren.Except(children, comparer)) { Entry(child).State = EntityState.Deleted; }
}
public async Task UpdateChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>();
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
var addedChildren = currentChildren.Except(existingChildren, comparer).AsEnumerable();
var deletedChildren = existingChildren.Except(currentChildren, comparer).AsEnumerable();
foreach (var child in currentChildren) { await AddOrUpdate(child); }
foreach (var child in addedChildren) { stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Added); }
foreach (var child in deletedChildren)
{
Entry(child).State = EntityState.Unchanged;
stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Deleted);
}
}
public static IEnumerable<string> GetChangedProperties(DbEntityEntry dbEntry)
{
var propertyNames = dbEntry.State == EntityState.Added ? dbEntry.CurrentValues.PropertyNames : dbEntry.OriginalValues.PropertyNames;
foreach (var propertyName in propertyNames)
{
if (IsValueChanged(dbEntry, propertyName))
{
yield return propertyName;
}
}
}
private static bool IsValueChanged(DbEntityEntry dbEntry, string propertyName)
{
return !Equals(OriginalValue(dbEntry, propertyName), CurrentValue(dbEntry, propertyName));
}
private static string OriginalValue(DbEntityEntry dbEntry, string propertyName)
{
string originalValue = null;
if (dbEntry.State == EntityState.Modified)
{
originalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null
? null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString();
}
return originalValue;
}
private static string CurrentValue(DbEntityEntry dbEntry, string propertyName)
{
string newValue;
try
{
newValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null
? null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString();
}
catch (InvalidOperationException) // It will be invalid operation when its in deleted state. in that case, new value should be null
{
newValue = null;
}
return newValue;
}
}
Then I call it like this
// POST: Admin/Events/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(Event #event)
{
if (!ModelState.IsValid) { return View(#event); }
await _db.AddOrUpdate(#event);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
I am trying to write master detail entry edit in Asp.net MVC model.
Fristly, let me show you two of my model classes.
Class name = Model\OrderDetailRepository.cs
public class OrderDetailRepository : IOrderDetailRepository
{
NorthwindEntities DB = new NorthwindEntities();
public IQueryable<Order_Detail> GetOrderDetailByOrderID(int OrderID)
{
return DB.Order_Details.Where(tableX => tableX.OrderID == OrderID);
}
public void InsertOrderDetail(Order_Detail _Order_Detail)
{
DB.Order_Details.AddObject(_Order_Detail);
}
public void UpdateOrderDetail(Order_Detail _Order_Detail)
{
DB.ObjectStateManager.ChangeObjectState(_Order_Detail, EntityState.Modified);
}
public void DeleteOrderDetailByOrderID(int OrderID)
{
Order_Detail _Order_Detail = DB.Order_Details.SingleOrDefault(x => x.OrderID == OrderID);
if (_Order_Detail != null)
DB.Order_Details.DeleteObject(_Order_Detail);
}
public void DeleteOrderDetailByCurrentRecord(Order_Detail _Order_Detail)
{
DB.Order_Details.DeleteObject(_Order_Detail);
}
public void Commit()
{
DB.SaveChanges();
}
}
Class name = Model\OrderMasterReposity.cs
public class OrderMasterReposity : IOrderMasterRepository
{
NorthwindEntities DB = new NorthwindEntities();
public IQueryable<Order> GetOrderMasterByOrderID(int OrderID)
{
return DB.Orders.Where(tableX => tableX.OrderID == OrderID);
}
public void InsertOrderMaster(Order _Order)
{
DB.Orders.AddObject(_Order);
}
public void UpdateOrderMaster(Order _Order)
{
DB.ObjectStateManager.ChangeObjectState(_Order, EntityState.Modified);
}
public void DeleteOrderMaster(int OrderID)
{
Order _Order = DB.Orders.SingleOrDefault(x => x.OrderID == OrderID);
if (_Order != null)
DB.Orders.DeleteObject(_Order);
}
public void Commit()
{
DB.SaveChanges();
}
}
After all upper code, I need to invoke these two class from my controller class.
So let's assume that below code i will write at controller layer.
OrderMasterReposity _OrderMasterReposity = new OrderMasterReposity();
OrderDetailRepository _OrderDetailRepository = new OrderDetailRepository();
_OrderMasterReposity.InsertOrderMaster(new Order{....});
_OrderDetailRepository.InsertOrderDetail(new Order_Detail{....});
_OrderMasterReposity.Commit();
_OrderDetailRepository.Commit();
What my problem is the way I doing now is not so professional way.
Because I need to invoke Commit function more than one time.
So, please could anyone give me more nicer way to code at Model layer?
You can use Unit of Work, see this example:
http://iridescence.no/post/ASPNET-MVC-DataContext-and-The-Unit-of-Work-Pattern.aspx
Best thing is that u implement Repository pattern properly
Moreover, Order detail has no meaning without order so according to DDD you should not define separate repository for order_detail. i would do it something like
Order _order = OrderMasterRepository.GetOrder(orderID);
_order.Property1 = value1;
_order.Property2 = value2;
_order.Property3 = value3;
foreach(var orderDetail in _order.OrderDetails)
{
orderDetail.Property1 = avalue1;
orderDetail.Property2 = avalue2;
.
.
}
OrderMasterRepository.Commit();
above statements will save data both in order and orderDetail table.