Silverlight 4 with RIA - Binding joined table field to grid - c#

I have 2 tables that has a 1-1 relationship
I am attempting to use the .Include() for LINQ to try to pass a child table entity to a property so that I can bind a child table field to a grid, along with fields from the parent table. Originally it works fine like this and I'm able to bind to the grid but only get results from the BUGroupBuildings table. I also need to bind to a field in the vwBuisnessUnits table.
public IQueryable<BUGroupBuilding> GetBusinessUnitsBasedOnGroupID(int i)
{
var result = from d in this.ObjectContext.BUGroupBuildings
join b in this.ObjectContext.vwBusinessUnits on d.BU equals b.BU
where d.BUGroupID == i
orderby d.BU ascending
select d;
return result;
}
When I switch over to use the Include to bring back child table fields, I get an error
public IQueryable<BUGroupBuilding> GetBusinessUnitsBasedOnGroupID(int i)
{
var result = from d in this.ObjectContext.BUGroupBuildings
.Include("vwBuisnessUnits")
select d;
result = result.Where(w => w.BUGroupID == i).OrderBy(o => o.vwBusinessUnit.BU);
return result;
}
ERROR:
Load operation failed for query 'GetBusinessUnitsBasedOnGroupID'. A
specified Include path is not valid. The EntityType
'EQUITYDWModel.BUGroupBuilding' does not declare a navigation property
with the name 'vwBuisnessUnits'
Here's my entity
I've added the necessary [Include] in the metadata.
[MetadataTypeAttribute(typeof(BUGroupBuilding.BUGroupBuildingMetadata))]
public partial class BUGroupBuilding
{
internal sealed class BUGroupBuildingMetadata
{
// Metadata classes are not meant to be instantiated.
private BUGroupBuildingMetadata()
{
}
public string BU { get; set; }
[Include]
public BUGroup BUGroup { get; set; }
public int BUGroupBuildingsID { get; set; }
public Nullable<int> BUGroupID { get; set; }
[Include]
public vwBusinessUnit vwBusinessUnit { get; set; }
}
}

Did you generate the EntityModel from db or create it manually? Are you create a metada class manually?
Maybe some are wrong. Should be create a navigation property on client side (Web.g.cs file) like this:
/// <summary>
/// Gets or sets the associated <see cref="tblCustomer"/> entity.
/// </summary>
[Association("tblCustomer_tblInvoice", "uiCustomerId", "Id", IsForeignKey=true)]
[XmlIgnore()]
public tblCustomer tblCustomer
{
get
{
if ((this._tblCustomer == null))
{
this._tblCustomer = new EntityRef<tblCustomer>(this, "tblCustomer", this.FiltertblCustomer);
}
return this._tblCustomer.Entity;
}
set
{
tblCustomer previous = this.tblCustomer;
if ((previous != value))
{
this.ValidateProperty("tblCustomer", value);
if ((previous != null))
{
this._tblCustomer.Entity = null;
previous.tblInvoices.Remove(this);
}
if ((value != null))
{
this.uiCustomerId = value.Id;
}
else
{
this.uiCustomerId = default(Guid);
}
this._tblCustomer.Entity = value;
if ((value != null))
{
value.tblInvoices.Add(this);
}
this.RaisePropertyChanged("tblCustomer");
}
}
}
Please check the Entity Model and a relationships.

Ahhh. I found the solution to my problem. Thought I would share it with the community.
It turns out that I have to do a join and an Inlcude in the linq query when I have a custom relationship between 2 entities (in my case, a view and a table). When a relationship is defined in the model, it doesn't require an explicit join.
public IQueryable<BUGroupBuilding> GetBusinessUnitsBasedOnGroupID(int i)
{
var result = from d in this.ObjectContext.BUGroupBuildings
join b in this.ObjectContext.vwBusinessUnits on d.BU equals b.BU
where d.BUGroupID == i
orderby d.BU ascending
select d;
var r2 = from d2 in ((ObjectQuery<BUGroupBuilding>)result)
.Include("vwBusinessUnit")
select d2;
return r2;
}

Related

Custom Selector Attribute - 'Customer' Cannot be found in the system

I made a custom selector that only displays the customers of the current user but when I select a customer I get the error: 'Customer' Cannot be found in the system.
The code for the Custom selector and how I implemented it on the DAC:
[PXNonInstantiatedExtension]
public class SO_SOOrder_ExistingColumn : PXCacheExtension<PX.Objects.SO.SOOrder>
{
#region CustomerID
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXForeignReference(typeof(Field<SOOrder.customerID>.IsRelatedTo<BAccount.bAccountID>))]
[SalesRepCustomer]
public int? CustomerID { get; set; }
#endregion
}
public class SalesRepCustomer : PXCustomSelectorAttribute
{
public SalesRepCustomer() : base(typeof(Customer.acctCD))
{
this.DescriptionField = typeof(Customer.acctCD);
}
protected virtual IEnumerable GetRecords()
{
foreach (Customer pc in PXSelect<Customer>.Select(this._Graph))
{
//Getting Current UserID
var cache1 = _Graph.Caches[BqlCommand.GetItemType(typeof(AccessInfo.userName))];
AccessInfo currentCacheObjecta = (AccessInfo)cache1.Current;
var userName = currentCacheObjecta.UserName;
SalesPerson person = PXSelect<SalesPerson, Where<SalesPerson.descr, Equal<Required<AccessInfo.userName>>>>.Select(_Graph, userName);
if (person != null)
{
CustSalesPeople custSalesPeople = PXSelect<CustSalesPeople, Where<CustSalesPeople.salesPersonID, Equal<Required<SalesPerson.salesPersonID>>, And<CustSalesPeople.bAccountID, Equal<Required<CustSalesPeople.bAccountID>>>>>.Select(_Graph, person.SalesPersonID, pc.BAccountID);
//return all customers related to this SalesPersonID in the CustSalesPeople table
if (!(custSalesPeople is null))
{
yield return pc;
}
}
else
{
//current user is not a sales person
//return all of the customers
yield return pc;
}
}
}
}
Screenshot of the selector on the Sales Order Screen:
Any help on this will be appreciated
You should have the constructor look something like
public SalesRepCustomer() : base(typeof(Customer.bAccountID))
{
this.DescriptionField = typeof(Customer.acctName);
this.SubstituteKey = typeof(Customer.acctCD);
}
The first type that you pass to the base constructor is the type of the value that will be used for the field.
In this case you want the customer id(an int) and you are currently using the AcctCD(a string) field. The description field would typically be the name of the customer account and the substitute key will make it so that the users see the AcctCD instead of the the customer ID(the actual value) which is just an integer.

Entity Framework - Many to Many relationship not saving to database

I have stumbled upon a problem with Entity Framework this morning.
I have following code mapping a modified entity and saving it into database.
public Group Save(Group x)
{
using (var db = new HostContext())
{
db.Projects.Attach(x.Project);
if (x.ID != 0)
{
db.AttachableObjects.Attach(x);
var manager = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager;
manager.ChangeObjectState(x, EntityState.Modified);
}
else
{
db.AttachableObjects.Add(x);
}
db.SaveChanges();
return x;
}
}
I call Save method with existing group as a parameter. Group contains one user I want to add as a member.
The method finishes successfully, however the relationship is not persisted in database.
Any help is very appreciated.
EDIT: These are my classes
class User : AttachableObject
{
...
private List<Group> memberof;
[DataMember]
[InverseProperty("Members")]
public List<Group> MemberOf
{
get { return memberof; }
set { memberof = value; }
}
...
}
class Group : AttachableObject
{
...
private List<User> members;
[DataMember]
[InverseProperty("MemberOf")]
public List<User> Members
{
get { return members; }
set { members = value; }
}
...
}
EDIT2: This is where the Save method is called
public Group AcceptInvite(int id)
{
var mapper = new InviteMapper();
var userMapper = new UserMapper();
var groupMapper = new GroupMapper();
var invite = mapper.Find(id);
if (invite != null)
{
var group = groupMapper.Find(invite.GroupID);
var user = userMapper.Find(invite.InviteeID);
group.Members.Add(user);
mapper.Delete(invite.ID);
return groupMapper.Save(group);
}
return null;
}
EDIT3: My mappers
public class GroupMapper
{
public Group Find(int id)
{
using (var db = new HostContext())
{
return db.AttachableObjects
.Include("Project")
.OfType<Group>().FirstOrDefault(x => x.ID == id);
}
}
}
The rest of the mappers is the same, only using their own tables.
You are not changing the relationship info of Project, you are only setting x to modified, relationship info must be changed explicitly.
So x.Project must have some property that points back to Group, you need to set it so the change is recorded.
I am guessing that x is resurrected via some deserialization process?

filtering list using linq

I am writing to seek help, in how can I filter data and then merge two results, below:
Update code*
public class TestController : ApiController
{
private cdw db = new cdw();
public HttpResponseMessage get([FromUri] Query query)
{
var data = db.data_qy.AsQueryable();
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
if (!string.IsNullOrEmpty(query.tag))
{
var ids = query.tag.Split(',');
data = data.Where(c => c.TAG.Any(t => ids.Contains(t)));
}
if (!string.IsNullOrEmpty(query.name))
{
var ids = query.name.Split(',');
data = data.Where(c => c.NAME.Any(t => ids.Contains(t)));
}
if (!data.Any())
{
var message = string.Format("No data found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, data);
}
}
entity class:
public partial class data_qy
{
public int ID { get; set; }
public string Name { get; set; }
public string TAG { get; set; }
public string TAG_IS { get; set; }
[Newtonsoft.Json.JsonProperty(PropertyName = "Date")]
public Nullable<System.DateTime> UploadDate { get; set; }
}
Sample Dataset:
Name Tag Tag_IS
AMCAR 2013-5 03065EAC9
ARES 2006-6RA 04009JAA9
ARES 2012-1A 04013TAB9
ATOM 2003-I A 0182690668
BACM 2006-2 AM 05950EAG3
BCAP 2007-AA3 05530VAN9
BCAP 2007-AA3 05530VAN9
BCJAF 9 C 0312888037
BLNDLN 0 0213093627
BLNDLN 0 0213093627
The underlying SQL query should resemble:
select *
from [dbo].[data_qy]
where TAG like '%78473TAC4%'
or TAG LIKE '%05946XYZ0%'
OR NAME LIKE '%TAP%'
OR NAME LIKE '%STORM%'
Using the following list method above, when I run a query such as (api/test/tag=78473,12669,05946,... (30 values)), i get a -- Exception Message:
Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries
Am I missing something. Any help would be most appreciated.
Thanks
I'm not in a situation to test this on the spot, but I suspect that either of the following should work:
public class TestController : ApiController
{
private cdw db = new cdw();
public HttpResponseMessage get([FromUri] Query query)
{
IQueryable<data_qy> data = null;
if (!string.IsNullOrEmpty(query.tag))
{
var ids = query.tag.Split(',');
var dataMatchingTags = db.data_qy.Where(c => ids.Any(id => c.TAGS.Contains(id)));
if (data == null)
data = dataMatchingTags;
else
data = data.Union(dataMatchingTags);
}
if (!string.IsNullOrEmpty(query.name))
{
var ids = query.name.Split(',');
var dataMatchingName = db.data_qy.Where(c => ids.Any(id => c.NAME.Contains(id)));
if (data == null)
data = dataMatchingName;
else
data = data.Union(dataMatchingName);
}
if (data == null) // If no tags or name is being queried, apply filters to the whole set of products
data = db.data_qy;
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
var materializedData = data.ToList();
if (!materializedData.Any())
{
var message = string.Format("No data found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, materializedData);
}
}
I also suspect that you don't need to check against Null in your query, since EF will understand that when transforming the Expression Tree to SQL, but if needed, you can add it.
That would remove the need to use the foreach, the Aggregate and the call to Count. and results in a much simpler query that should use the IN operator in SQL.
Currently you're executing the same query multiple times (the call to .Any at the end will execute the query and then passing the data variable will execute it again. This can be very costly. Instead, materialize the results and act upon that, as above.

Save detached object graph using Entity Framework code first causes Primary Key violation

I'm trying to save an object graph of POCOs I have mapped to EF6 using Code First fluent notations.
Upon saving the object graph however, I stumble upon primary key violation exceptions.
The object graph is quite simple:
One Issue can contain multiple WorkItems with each one Author (as User).
The objects are populated externally (using a Web API)
When I attempt to save an issue with two workitems which refer to the same author, I would expect the issue to be inserted, the workitems to be inserted and one author to be inserted, and the other one to be referenced or be updated.
What happens however is that the issue is inserted, the workitems are inserted and both references to the same user are inserted, resulting in a primary key violation.
Simplified Issue object:
public class Issue
{
public Issue()
{
WorkItems = new List<WorkItem>();
}
public string Id { get; set; }
private List<WorkItem> _workItems;
public List<WorkItem> WorkItems
{
get { return _workItems ?? new List<WorkItem>(); }
set { _workItems = value; }
}
}
Simplified WorkItem:
public class WorkItem
{
public string Id { get; set; }
public string AuthorLogin
{
get; set;
}
private WorkItemAuthor _author;
public WorkItemAuthor Author
{
get { return _author; }
set { _author = value;
if (value != null)
{
AuthorLogin = value.Login;
}
else
{
AuthorLogin = string.Empty;
}
}
}
}
Simplified user object:
public class User
{
public string Login { get; set; }
public string FullName { get; set; }
}
Their Code-first configurations:
internal IssueConfiguration()
{
HasKey(x => x.Id);
HasMany(x => x.WorkItems);
}
internal WorkItemConfiguration()
{
HasKey(x => x.Id);
HasRequired(p => p.Author)
.WithMany(b => b.WorkItems)
.HasForeignKey(x=>x.AuthorLogin);
}
internal UsersConfiguration()
{
HasKey(x => x.Login);
}
All quite straightforward. Upon database create, de tables look fine and dandy too, with FKs on the columns where one would expect them
Now when saving the issue, it would have been nice if the object graph would be inserted, and the reference to existing objects would be recognized automagically and optionally inserted or referenced only.
I attempt to add issues accordingly:
using (var db = new Cache.Context())
{
if (db.Issues.Any(e => e.Id == issue.Id))
{
db.Issues.Attach(issue);
db.Entry(issue).State = EntityState.Modified;
}
else
{
db.Issues.Add(issue);
}
db.SaveChanges();
}
Is the solution to this issue that I walk through the object graph to manually add or attach the other objects in the graph too? I would expect by defining the proper Foreign Key values these references would be recognized.
I finally ended up doing something similar to this, quite laborious and I would still like to find a better way.
Finding out whether an entity is already attached or exists in the database turned out to be pollute the model too much (implementing IEquatable<T> is fine, but I think implementing IEntityWithKey on my POCOs pollutes the POCO too much. (and till that did not seem to suffice tracking entities in the context)
internal static void Save(this List<Issue> issues)
{
using (var db = new Context())
{
foreach (var issue in issues.ToList())
{
foreach (var workItem in issue.WorkItems.ToList())
{
if (workItem.Author != null)
{
var existing = db.Users.SingleOrDefault(e => e.Login == workItem.Author.Login);
if (existing == null)
{
db.Users.Add(workItem.Author);
}
else
{
//Update existing entities' properties
existing.Url = workItem.Author.Url;
//Replace reference
workItem.Author = existing;
}
db.SaveChanges();
}
var existingWorkItem = db.WorkItems.SingleOrDefault(e => e.Id == workItem.Id);
if (existingWorkItem == null)
{
db.WorkItems.Add(workItem);
}
else
{
//Update existing entities' properties
existingWorkItem.Duration = workItem.Duration;
//Replace reference
issue.WorkItems.Remove(workItem);
issue.WorkItems.Add(existingWorkItem);
}
db.SaveChanges();
}
var existingIssue = db.Issues.SingleOrDefault(x => x.Id == issue.Id);
if (existingIssue == null)
{
db.Issues.Add(issue);
}
else
{
//Update existing entities' properties
existingIssue.SpentTime = issue.SpentTime;
}
db.SaveChanges();
}
}
}
There is a small bug in the Issue object.
"return _workItems ?? new List();" could return a new WorkItem on every get if _workItems ever became null. Here is the fixed version.
public class Issue {
public Issue() {
WorkItems = new List<WorkItem>();
}
public String Id {
get; set;
}
public List<WorkItem> WorkItems { get; private set; }
}

Entity Framework not saving modified children

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");
}

Categories