I have searched around for a good amount of time trying to find a satisfactory solution to the orphan issue common in EF.
One of the simplest forms of orphaning is the clearing of a collection of entities. The relationship between the entities is removed but the child entities still remain in the database.
My requirements: -
The clearing of the collection occurs in the domain and I want to simply be able to call clear and no more.
Any logic to figure out whether the relationship between the parent and child has been broken resulting in the delete needs to be encapsulated within the repository / DbContext.
I don't want to have to 'dirty' the domain with anything additional in order to solve this problem. This includes back references.
I suspect that this can't be solved as I have have spent considerable time looking for solutions but I ask out of hope!
Areas I have looked at are the ChangeTracker and any possible events which I can hook into, similar to AssociationChanged event which has popped up in various places. Something, somewhere in the DbContext must know that this relationship has been broken. How to access it, that is the question?
Thanks.
Can you try the following solution? Mb it fits your needs. The DeleteOrphans extension method must be called between DetectChanges and SaveChanges methods.
public static class DbContextExtensions
{
private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();
public static void DeleteOrphans( this DbContext source )
{
var context = ((IObjectContextAdapter)source).ObjectContext;
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var entityType = entry.EntitySet.ElementType as EntityType;
if (entityType == null)
continue;
var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
var props = entry.GetModifiedProperties().ToArray();
foreach (var prop in props)
{
NavigationProperty navProp;
if (!navPropMap.TryGetValue(prop, out navProp))
continue;
var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
var enumerator = related.GetEnumerator();
if (enumerator.MoveNext() && enumerator.Current != null)
continue;
entry.Delete();
break;
}
}
}
private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
{
var result = type.NavigationProperties
.Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
.Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
.Where(v => v.DependentProperties.Length == 1)
.ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);
return new ReadOnlyDictionary<string, NavigationProperty>(result);
}
}
Related
I'm creating a very simple CRUD app using Polymer js but facing some issues while editing the records.
Here is the code for add/edit:
_addTodo() {
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
this.users[foundIndex] = this.user;
this.set('users', this.users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Although I could see the values inside the users object getting changed in the browser console but it's not getting changed in the DOM/UI.
If I'm using a static user object like below then it works:
_addTodo() {
if(this.user.id) {
var users = [
{
id: 1,
name: 'xyz',
age: 21
},
{
id: 2,
name: 'xyz123',
age: 5
}
]
this.set('users', users);
console.log(this.users);
}
else {
this.user.id = Math.floor((Math.random() * 100000) + 1);
this.push('users', this.user);
}
this.user = {};
}
Even I have used "notifyPath" instead of "set" but that is also not working.
Could anyone please suggest what I am doing wrong here for which the user object is not getting changed in DOM?
Update:
As suggested below, I'm using splice for updating the array but still it's not working.
JSfiddle - https://jsfiddle.net/ansumanmishra/8490y4q8/1/
this.users[foundIndex] = this.user;
this.set('users', this.users);
Updating the DOM takes performance. Whenever set is used, Polymer dirty checks every value in the array, but you have already set the array to it's new value so when it compares (basically, it compares with itself), Polymer wont detect any updates and therefor wont update the DOM.
You can't however do this as solution: var newUserArr = this.users, and then modify newUserArr because objects and arrays only create references to each other.
var a = [1]
var b = a
b[0] = 2
console.log(a) // gives [2]
You will only end up with the same thing as above: Polymer dirty checking the array with itself. Remove the reference with JSON.stringify, and then set the new array. I use this method all the time.
if(this.user.id) {
let foundIndex = this.users.findIndex( x => x.id === this.user.id);
// Remove references
var newUserArr = JSON.parse(JSON.stringify(this.users)));
newUserArr[foundIndex] = this.user;
this.set('users', newUserArr);
}
EDIT
However, when you want to edit something, you also create a reference from the object in the array, so when you type in your inputs, you will update the object in the existing array users.
I fiddled with your fiddle, and now it works. What I did was that I added JSON.parse(JSON.stringify()) in the method _editUser() too.
http://jsfiddle.net/c6h2hwch/
From "Set a property or subproperty by path": "calling set on an object property won't cause Polymer to pick up changes to the object's subproperties, unless the object itself changes." Note example:
// DOES NOT WORK
this.profile.name = Alex;
this.set('profile', this.profile);
You need to replace this.profile with a new profile object, or update the path of each individual member of profile.
This isn't an observable change:
this.users[foundIndex] = this.user;
this.set('users', this.users);
You're modifying the array that this.users points to (in a way Polymer can't detect) and then setting this.users to the same array—this.set('users', this.users) is the same operation as this.users = this.users.
You have a couple options. One is to use this.splice:
this.splice('users', foundIndex, 1, this.user);
This says, "remove 1 item at foundIndex and insert this.user in its place.
The other option is to create a copy of the array (with Array.prototype.slice—note that's slice, not splice) to make the change observable:
const nextUsers = this.users.slice();
nextUsers[foundIndex] = this.user;
this.users = nextUsers;
I recommend this.splice because it doesn't make Polymer do quite as much work when re-rendering e.g. a dom-repeat for the array.
I have a list of possible entries stored in a database. I sort and display these entries into a new view depending on an entry put into a textbox, or from clicking on an html actionlink. The main idea of this can be seen below.
public ActionResult Index(string sortOrder, string searchString, int? sortType)
{
if (!String.IsNullOrEmpty(searchString))
{
if (sortType == 1)
{
//Sort the results strictly based on the HTML header options
applications = applications.Where(s => s.Business.Equals(searchString));
}
else
{
//sort based on the input of the form
applications = applications.Where(s => s.Business.Contains(searchString)
|| s.ApName.Contains(searchString));
}
}
return View(applications.ToList());
}
The first part of this if statement is activated by clicking a link like so,
<li>#Html.ActionLink("CITY", "index", "app", new {SearchString = "city", sortType=1}, null)</li>
Although my code is working, I'm just wondering if there is a more "proper" way of doing what I coded, without having to pass a sortType variable to determine whether to sort based on the form or the actionlink. I'm sure what I have done is quite a bad way of doing it, but I'm very new to this.
I'm not sure if this is what you are after because your question keeps talking about sorting but your code is talking about filtering. You could try to specify default values so the Where statements short-circuit when those criteria are not populated. Ex:
public ActionResult Index(string city = null, string business = null){
var model = applications
.Where(x => city == null || x.City.Contains(city))
.Where(x => business == null || x.Business == business);
return View(model);
}
very new to SharedObjects, but essentially all I want to do is let a user answer a question once and not allow them to answer again, how is it possible. This is what I have below?
/*if(_question.data.seenQuestion == "true") {
cookie_txt.text = "COOKIE FOUND";
} else {
cookie_txt.text = "NO COOKIES";
}*/
var _question:SharedObject;
_question.data.seenQuestion = "true";
_question.flush();
_question.close();
You're very close. It looks like you're not actually creating a SharedObject, to do that you would use the method getLocal:
var _question:SharedObject = SharedObject.getLocal("questionData");
Additionally, since SharedObject supports primitive types (String, int, Number, Object, Boolean, etc), you should store a Boolean instead of a String:
if(_question.data.seenQuestion)
{
cookie_txt.text = "COOKIE FOUND";
}
else
{
cookie_txt.text = "NO COOKIES";
}
_question.data.seenQuestion = true;
_question.flush();
Lastly, if you're using a local shared object (more common), you don't need to call close().
You do need to create your SharedOBject or else your code will throw errors:
var _question:SharedObject = SharedOBject.getLocal("myapp");
No need for flush or close just assign/change some data and that's it.
_question.data.seenQuestion = "true";
However SharedObject is not a good solution for persistent data. User can erase it/reset it, etc ...
Using the Client Object Model I am looking for the most efficient way to search a SharePoint server and determine if a specific subsite exists given its unique ID (GUID). We are storing the GUID in our external system because we need to get back to the site and the GUID is the only property that can not change. I know that CAML can be used to search for data within a specific site. However, I haven't been able to find an API that will do this for subsites. I am forced to do a recursive search and use a for loop. In my case, we could have thousands of sites nested on our server.
This logic does one level -- but is not efficient when thousands of subsites exists.
public bool SiteExists(ClientContext context, string myGuid)
{
Web oWebsite = context.Web;
context.Load(oWebsite, website => website.Webs, website => website.Title, website => website.Description, website => website.Id);
context.ExecuteQuery();
for (int i = 0; i != oWebsite.Webs.Count; i++)
{
if (String.Compare(oWebsite.Webs[i].Id.ToString(), myGuid, true) == 0)
{
return true;
}
}
return false;
}
public bool SiteExists(ClientContext context, string myGuid) {
Guid id = new Guid(myGuid);
Site site = context.Site;
Web foundWeb = site.OpenWebById(id);
context.Load(foundWeb);
context.ExecuteQuery();
if(foundWeb != null) {
return true;
}
return false;
}
I create a mx:tree with flex and its data provider is an array collection. In addition, this array collection is set by using a database. This process is handled with an event listener function. Database returns data to array collection asynchronously. And this is the problem that when flex application is started array collection is not fully initialized. Hence, mx:tree is incomplete. Here is the code segment:
protected function populateTreeNode(node:Object):void
{
if (node != null && node["className"] != "InventoryCategory") return;
var categoryId:Number = 0;
if (node != null)
categoryId = node["id"];
DAOUtil.loadAll("InventoryCategory", EventUtil.handleWithArgs(popoluateTreeNodeHandler, [node, "InventoryCategory"]), "categoryId", categoryId.toString());
DAOUtil.loadAll("InventoryItem", EventUtil.handleWithArgs(popoluateTreeNodeHandler, [node, "InventoryItem"]), "categoryId", categoryId.toString());
}
protected function popoluateTreeNodeHandler( event : Event , nodeCategory:Object, typeName:String): void
{
var items:Array = DAOUtil.getArray(event, typeName);
items = LangUtil.fromNameField(items);
if (nodeCategory != null)
nodeCategory["children"] = items;
else
inventoryArray.addAll(new ArrayCollection(items));
for each (var item:Object in items) populateTreeNode(item);
}
This function tries to initialize array collection recursively and at the end of the populateTreeNodeHandler function it is fully initialized. But when flex application is started, it is sometimes incomplete. Is there any solution to this problem?
Problem is fixed. Problem is the fulfilling the data provider array two times. One of them fulfill the array with category items correctly but the other one empty the data provider array.