Problems adding new property to existing node with Neo4jClient - neo4j

My beginner problems with using Neo4jClient unfortunately continues :) I have a simple class WorkEntity for which I have an update query:
public class WorkEntity
{
public string Id { get; set; }
public string Description { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
var query = graphClient.Cypher
.Match("(work:Work)", "(city:City)", "(profession:Profession)", "(company:Company)", "(oldCompany:Company)", "(oldProfession:Profession)", "(oldCity:City)",
"(user:User)-[r1]->work", "work-[r2]->oldProfession", "work-[r3]->oldCompany", "work-[r4]->oldCity")
.Where((WorkEntity work) => work.Id == model.Id)
.AndWhere((CityEntity city) => city.Id == model.CityId)
.AndWhere((CityEntity oldCity) => oldCity.Id == oldModel.CityId)
.AndWhere((ProfessionEntity profession) => profession.Id == model.ProfessionId)
.AndWhere((ProfessionEntity oldProfession) => oldProfession.Id == oldModel.ProfessionId)
.AndWhere((CompanyEntity company) => company.Id == model.CompanyId)
.AndWhere((CompanyEntity oldCompany) => oldCompany.Id == oldModel.CompanyId)
.AndWhere("type(r1) = 'CURRENT'")
.AndWhere("type(r2) = 'WORK_AS_PROFESSION'")
.AndWhere("type(r3) = 'WORK_AT_COMPANY'")
.AndWhere("type(r4) = 'WORK_IN_CITY'")
.Set("work = {updatedWork}")
.WithParam("updatedWork", updatedEntity);
//If Date has been set delete current relationships
if (oldModel.EndDate == DateTime.MinValue && model.EndDate > DateTime.MinValue)
{
query = query.Delete("r1");
}
if (oldModel.ProfessionId != model.ProfessionId)
{
query = query.Delete("r2")
.CreateUnique("work-[:WORK_AS_PROFESSION]->profession");
}
if (oldModel.CompanyId != model.CompanyId)
{
query = query.Delete("r3")
.CreateUnique("work-[:WORK_AT_COMPANY]->company");
}
if (oldModel.CityId != model.CityId)
{
query = query.Delete("r4")
.CreateUnique("work-[:WORK_IN_CITY]->city");
}
query.ExecuteWithoutResults();
Modifying all the relationsships works as expected but if for example Description is null from the beginning above query won't add Description to an existing entity (or update any other of the properties) even though it is set in updatedEntity. If I simplify the query as below Description is added/removed and existing changed properties are updated correctly. How can that be? I really would prefer to do the whole query in one transaction.
var query = graphClient.Cypher
.Match("(work:Work)")
.Where((WorkEntity work) => work.Id == model.Id)
.Set("work = {updatedWork}")
.WithParam("updatedWork", updatedEntity);

I think the reason the query does nothing when the CityIds are the same in my gist is because the MATCH clause:
work-[r4]-oldCity
is not matching anything, so the match fails and the query does nothing.
In effect - if I get this right - you're attempting two queries:
Update work
Delete & Create new relationships
I think you should check out the WITH keyword, as it should help your query...
WITH allows you to chain your cypher queries together, so you could change your query to:
var query = Client.Cypher
.Match("(work:Work)")
.Where((WorkEntity work) => work.Id == model.Id)
.Set("work = {updatedWork}")
.With("work")
.Match("(city:City)", "(profession:Profession)", "(company:Company)", "(oldCompany:Company)", "(oldProfession:Profession)", "(oldCity:City)",
"(user:User)-[r1]->work", "work-[r2]->oldProfession", "work-[r3]->oldCompany", "work-[r4]->oldCity")
.Where((WorkEntity work) => work.Id == model.Id)
.AndWhere((CityEntity city) => city.Id == model.CityId)
.AndWhere((CityEntity oldCity) => oldCity.Id == oldModel.CityId)
.AndWhere((ProfessionEntity profession) => profession.Id == model.ProfessionId)
.AndWhere((ProfessionEntity oldProfession) => oldProfession.Id == oldModel.ProfessionId)
.AndWhere((CompanyEntity company) => company.Id == model.CompanyId)
.AndWhere((CompanyEntity oldCompany) => oldCompany.Id == oldModel.CompanyId)
.AndWhere("type(r1) = 'CURRENT'")
.AndWhere("type(r2) = 'WORK_AS_PROFESSION'")
.AndWhere("type(r3) = 'WORK_AT_COMPANY'")
.AndWhere("type(r4) = 'WORK_IN_CITY'")
.WithParam("updatedWork", updatedEntity);
This will execute the update and then the rest.
Another keyword that might be worth a look would be MERGE but I think WITH should help you out here.

Thanks Chris, I realise that my understanding for Neo4j is still quite limited :) Your suggestion above updates the work entity properties correctly but not the relationsships. I don't really get why the work-[r4]-oldCity relationship doesn't match anything but anyway I took advantage of your note about this and after some trial and error finally got the query to work correctly like:
var oldModel = GetWorkModel(model.Id);
var updatedEntity = Mapper.Map<WorkEntity>(model);
var query = graphClient.Cypher
.Match("(work:Work)", "(city:City)", "(profession:Profession)", "(company:Company)",
"(user:User)-[r1:CURRENT]->work", "work-[r2:WORK_AS_PROFESSION]->()", "work-[r3:WORK_AT_COMPANY]->()", "work-[r4:WORK_IN_CITY]->()")
.Where((WorkEntity work) => work.Id == model.Id)
.AndWhere((CityEntity city) => city.Id == model.CityId)
.AndWhere((ProfessionEntity profession) => profession.Id == model.ProfessionId)
.AndWhere((CompanyEntity company) => company.Id == model.CompanyId)
.Set("work = {updatedWork}")
.WithParam("updatedWork", updatedEntity);
if (oldModel.EndDate == DateTime.MinValue && model.EndDate > DateTime.MinValue)
query = query.Delete("r1");
if (oldModel.ProfessionId != model.ProfessionId)
query = query.Delete("r2").CreateUnique("work-[:WORK_AS_PROFESSION]->profession");
if (oldModel.CompanyId != model.CompanyId)
query = query.Delete("r3").CreateUnique("work-[:WORK_AT_COMPANY]->company");
if (oldModel.CityId != model.CityId)
query = query.Delete("r4").CreateUnique("work-[:WORK_IN_CITY]->city");
query.ExecuteWithoutResults();

Related

check for null before getting property in linq to sql query

I have a query that looks like this, if there is no RegisteredInstructor an error gets thrown. Is there a way to fix this or check during the linq query?
viewModel.SpaceEvents = yogaSpace.YogaSpaceEvents.Where(j => j.EventDateTime >= DateTime.Now).Select(j => new EventResult
{
RegsiteredInstructorName = j.RegisteredInstructor.Name,
RegisteredInstructorId = j.RegisteredInstructor.RegisteredTeacherId,
RegisteredTeacherImage = j.RegisteredInstructor.RegisteredTeacherImage
}).ToList();
where RegisteredInstructor is a
public virtual Profile RegisteredInstructor { get; set; }
in my YogaSpaceEvents entity.
Try this.This could fix your error, checking j.RegisteredInstructor is null or not.
viewModel.SpaceEvents = yogaSpace.YogaSpaceEvents
.Where(j => j.EventDateTime >= DateTime.Now)
.Select(j => new EventResult
{
RegsiteredInstructorName = j.RegisteredInstructor!= null?j.RegisteredInstructor.Name:"",
RegisteredInstructorId = j.RegisteredInstructor!= null?j.RegisteredInstructor.RegisteredTeacherId:0,
RegisteredTeacherImage = j.RegisteredInstructor!= null?j.RegisteredInstructor.RegisteredTeacherImage:""
}).ToList();

how to handle null value in many to many relationship

I am trying to achieve something like this:
if there is a matching id then filter result according to it otherwise bypass the condition
.Where(x => x.NeighbourhoodId == (id ?? x.NeighbourhoodId)
but i am not getting correct syntax with many to many relationship:
public JsonResult GetPost(int? id, int? tagid)
{
var ret = from data in db.Posts.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.OrderByDescending(x => x.PostedDate)
.Where(x => x.NeighbourhoodId == (id ?? x.NeighbourhoodId)
&& x.Tags.Any(t => t.TagId == tagid))
.ToList()
select new
{
TagName = string.Join(",", data.Tags.Select(t => t.TagName)),
Message = data.Message,
// and other related stuff
}
here, as u can see,this where clause contains multiple conditions i want to filter post.There will be only one parameter with value. means if id parameter have value then tagid will be null and if tagid is null then id would have some value.
now, i want if there is null value in tagid then still this query should run. right now, its not working becoz in database, there is no post with empty tagid or null .how to do it. any suggestions??
If I understand correctly, you need to build the filter dynamically based on the passed parameters like this
var posts = db.Posts
.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.OrderByDescending(x => x.PostedDate);
if (id != null)
posts = posts.Where(x => x.NeighbourhoodId == id.Value);
if (tagid != null)
posts = posts.Where(x => x.Tags.Any(t => t.TagId == tagid.Value));
var ret = from data in posts
// ... the rest

Remove a list of objects and add new ones efficiently using Entity Framework

I'm taking into two multiselect lists into my Edit Action Post method along with a viewmodel. For each multiselect I want to loop through each object and remove what is not selected and add what is.
In this case, I'm adding and removing users from the project that are of a certain role type. Initially I was thinking db.SaveChages() on each iteration but that seems inefficient? Any suggestions for a better approach? Currently this doesn't work... Pardon if I'm way off base, this is my 4th week learning MVC. Thanks in advance!
// POST: Projects/Edit
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrator")]
public ActionResult Edit(ProjectEditViewModel vm, int ProjectStatusId)
{
if (ModelState.IsValid)
{
var project = db.Project.Find(vm.ProjectId);
project.Name = vm.ProjectName;
project.ProjectStatusId = ProjectStatusId;
var users = db.Users.Where((u => u.Projects.Any(ui => ui.ProjectId == vm.ProjectId)));
var currentDevs = users.Where(u => u.Roles.Any(ur => ur.RoleId == db.Roles.FirstOrDefault(r => r.Name == "Developer").Id));
var currentPMs = users.Where(u => u.Roles.Any(ur => ur.RoleId == db.Roles.FirstOrDefault(r => r.Name == "Project_Manager").Id));
if (currentDevs != null)
{
foreach (var cd in currentDevs)
{
project.Users.Remove(cd);
}
}
if (currentPMs != null)
{
foreach (var cpm in currentPMs)
{
project.Users.Remove(cpm);
}
}
if (vm.SelectedDevs != null)
{
foreach (var dev in vm.SelectedDevs)
{
var developer = users.FirstOrDefault(a => a.DisplayName == dev);
project.Users.Add(developer);
}
}
if (vm.SelectedPMs != null)
{
foreach (var pm in vm.SelectedPMs)
{
var projMgr = users.FirstOrDefault(a => a.DisplayName == pm);
project.Users.Add(projMgr);
}
}
db.Entry(project).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Details", new { id = vm.ProjectId });
}
return View(vm);
}
Thought I'd swing back around and post my solution for 1 of the two role types. Same logic was applied to the other (1st solution to SO woot!!!)
var devRoleId = db.Roles.FirstOrDefault(r => r.Name == "Developer").Id;
var users = db.Users.ToList();
//currently assigned developers
var currentDevs = (from p in project.Users
where p.Roles.Any(r => r.RoleId == devRoleId)
select p).ToList();
// if the new list is null and old list is not null, remove the old list members
if (vm.SelectedDevs == null)
{
if(currentDevs != null)
{
foreach (var d in currentDevs)
{
project.Users.Remove(d);
}
}
}
//if the new list is not null
if (vm.SelectedDevs != null)
{
if (currentDevs == null) //if the old list is null, add the new list members
{
foreach(var nd in vm.SelectedDevs)
{
project.Users.Add(users.FirstOrDefault( u => u.DisplayName == nd));
}
}
else //if the old list is not null, compare each new list member to old and if its new list member is truely new, add them
{
foreach(var nd in vm.SelectedDevs)
{
if(!currentDevs.Any(cd => cd.DisplayName == nd))
project.Users.Add(users.FirstOrDefault( u => u.DisplayName == nd));
}
}
}
This should work for you. You don't want to remove all the users and re-add them each time. That's going to cause a lot of problems. Instead, you remove only the ones that have been de-selected, and then add only the ones that have been newly-selected (did not exist in the list before).
var devRoleId = db.Roles.FirstOrDefault(r => r.Name == "Developer").Id;
var pmRoleId = db.Roles.FirstOrDefault(r => r.Name == "Project_Manager").Id;
// Remove de-selected devs
project.Users.Where(u => u.RoleId == devRoleId && !vm.SelectedDevs.Contains(u.DisplayName))
.ToList().ForEach(u => project.Users.Remove(u));
// Add newly selected devs
var existingDevs = project.Users.Where(u => u.RoleId == devRoleId).Select(m => m.DisplayName);
db.Users.Where(u => vm.SelectedDevs.Exclude(existingDevs).Contains(u.DisplayName))
.ToList().ForEach(u => project.Users.Add(u));
// Remove de-selected PMs
project.Users.Where(u => u.RoleId == pmRoleId && !vm.SelectedPMs.Contains(u.DisplayName))
.ToList().ForEach(u => project.Users.Remove(u));
// Add newly selected PMs
var existingPMs = project.Users.Where(u => u.RoleId == pmRoleId).Select(m => m.DisplayName);
db.Users.Where(u => vm.SelectedPMs.Exclude(existingPMs).Contains(u.DisplayName))
.ToList().ForEach(u => project.Users.Add(u));

How do I get this ViewModel correct?

Utilizing MVC 5 and Entity Framework I am trying to set up my a ViewModel for my Index view for a show of Employees and their goals.
I have an employee table, an employeeMap table (join table with payload) and a goal table.
There is a one-to-many relationship between Employee and EmployeeMap and between Goal and EmployeeMap.
I am a total newbie and getting stuck with an index view, which initially should display employees, and when one employee is selected should display the goals of the employees.
I can't get my index action right:
var viewModel = new EmployeeGoals();
viewModel.Employees = db.Employees
.Include(d => d.Department)
.Include(e => e.Position)
.Include(m => m.EmployeeMaps)
.Where(d => d.OrganizationID == oid && d.Department.ManagerID == currentUser.EmployeeID)
.OrderBy(d => d.HireDate);
if (id != null)
{
ViewBag.EmployeeID = id.Value;
viewModel.EmployeeMaps = viewModel.Employees.Where(e => e.ID == id.Value).Single().EmployeeMaps;
viewModel.Goals = viewModel.EmployeeMaps.Where(e => e.EmployeeID == ViewBag.EmployeeID).Select(e => e.Goals);
}
if (goalID != null)
{
ViewBag.GoalID = goalID.Value;
viewModel.Activities = viewModel.Goals.Where(
x => x.ID == goalID).Single().Activities;
}
return View(viewModel);
I do get the viewmodel.employees populated correctly, but not the viewmodel.goals
oh, and the viewmodel is:
public class EmployeeGoals
{
public IEnumerable<EmployeeMap> EmployeeMaps { get; set; }
public IEnumerable<Employee> Employees { get; set; }
public IEnumerable<Goal> Goals { get; set; }
}
A hint to help me past this would be great.
Thanks
Got it working using a different approach:
viewModel.Employees = db.Employees
.Include(d => d.Department)
.Include(e => e.Position)
.Include(m => m.EmployeeMaps)
.Where(d => d.OrganizationID == oid && d.Department.ManagerID == currentUser.EmployeeID)
.OrderBy(d => d.HireDate);
if (id != null)
{
ViewBag.EmployeeID = id.Value;
viewModel.EmployeeMaps = viewModel.Employees.Where(e => e.ID == id.Value).Single().EmployeeMaps;
viewModel.Goals = from g in db.Goals
join m in db.EmployeeMaps on g.ID equals m.GoalID
join e in db.Employees on m.EmployeeID equals e.ID
where m.EmployeeID == id.Value
select g;
}
couldn't figure out how to get a result using navigation properties, so ended up with above solution. It does the job, but am I hitting the database too often?? Also, ended up with a mix of LINQ syntax'es - should go with just one of them, I know :-/
You should use SelectMany:
viewModel.Goals = viewModel.EmployeeMaps
.Where(e => e.EmployeeID == ViewBag.EmployeeID)
.SelectMany(e => e.Goals);
because EmployeeMaps.Where() is an IEnumerable, so a Select produces IEnumerable<IEnumerable<Goal>>. SelectMany flattens this into IEnumerable<Goal>.

Whether I should write, simple join for common records?

I am developing an ASP.NET MVC application. I have two queries I want to get common records from those queries.
Am I suppose to write simple join to get the common records ?
var poList =
(from po in db.PurchaseOrders
where po.CompanyId == companyId &&
po.PartyId == partyId &&
(po.IsDeleted == false || po.IsDeleted == null)
select po into newPO
select new
{
Name = newPO.PONo,
Id = newPO.Id
});
var poList2 = (db.Employees.Where(x => x.Id == EmpID)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Employees)
.Distinct()
.SelectMany(x => x.PurchaseOrders)
.Select(po => new { Name = po.PONo, Id = po.Id }));
I am trying to write the join but its asking for one more argument, how to write a simple join for common records ?
var finalPO = poList.Join(poList2).ToList();
You can use an overload of Join method to specify join conditions:
poList.Join(poList2, a => a.Name, b => b.Name, (a,b) => new { Name = b.PONo, Id = b.Id });
Union will give you all records from both:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.union.aspx
Join method will give you records that match on provided key, but requires more than just the two enumerables as parameters:
http://msdn.microsoft.com/en-us/library/bb534675.aspx
You don't need a join because you can apply the expressions in the first query to the "raw" result of the second one:
var poList2 = db.Employees.Where(x => x.Id == EmpID)
.SelectMany(x => x.Roles)
.SelectMany(x => x.Employees)
.Distinct()
.SelectMany(x => x.PurchaseOrders);
var result = from po in poList2
where po.CompanyId == companyId &&
po.PartyId == partyId &&
!po.IsDeleted
select po into newPO
select new
{
Name = newPO.PONo,
Id = newPO.Id
};
And if you need the result you originally intended to get from poList2:
var result2 = poList2.Select(po =>
{
Name = po.PONo,
Id = po.Id
};
So, generally speaking, you can do more with your query bodies if you postpone the projection to anonymous types.
Side note: I used !po.IsDeleted because I'd strongly recommend to make the field not nullable, with false as default value.

Resources