Unable to determine a valid ordering for dependent operations - asp.net-mvc

I'm using Entity Framework 4.1
I have a "DomainEntities" table that holds the common info for all my domain entities.
I have a users table the the UserID is a Foreign Key from "DomainEntities".
see EDMX:
When I run the following code i get an error:
Unable to determine a valid ordering for dependent operations.
Dependencies may exist due to foreign key constraints, model
requirements, or store-generated values.
The code:
static void addUserTest()
{
DomainEntity userToAdd = new DomainEntity()
{
EntityName = "Test User",
EntityTypeID = DomainEntity.eEntityType.User,
EntityCreationDate = new DateTime(),
EntityLastUpdateDate = new DateTime(),
EntityCreatorUserID = 0,
EntityUpdaterUserID = 0,
EntityParentID = null,
UserDetails = new User()
{
Username = "TestUser",
Password = "123",
FirstName = "Test",
LastName = "User"
}
};
using (var context = new CamelotDB())
{
context.DomainEntities.Add(userToAdd);
context.SaveChanges();
}
}
I cant understand what is the reason that EF can understand what is the INSERT order required,
It should be One record into "DomainEntities" and then one record into "Users".
What am I doing wrong ?

After searching for one more day I found it the problem was with the Creator and Updater self referenced foreign keys.
CreatorID is not Nullable so does UpdaterID and this is why EF requires the navigation properties to point to actual entities from the database so i added the following lines in the initializer of Test User.
EntityCreatorUserID = 0,
Creator = context.DomainEntities.Find(0),
EntityUpdaterUserID = 0,
Updater = context.DomainEntities.Find(0),

It seems that instead of having your User be related to your DomainEntity, you should make your User a subclass of DomainEntity. In the Entity Model designer, this is done by using the Inheritance tool (Double-click the Inheritance tool in the toolbox, then click once on the parent entity and once on the child entity.)
This more accurately describes the nature of a User; a User is a DomainEntity. Your current model, suggests that a User is related to a DomainEntity, which doesn't seem right.

Related

Trying to use Entity Framework method for multiple adds to database

I have an ASP.NET MVC app using Entity Framework from our SQL Server backend.
Goal is to create ~18 WPackage entries via a foreach loop:
foreach (var dbitem in dbCList)
The code works for a single WPackage entry, but we have a request from the customer to create 300+ WPackages, so trying to use the Entity Framework code for a single "Add" and loop to create 300+ adds.
The T-SQL would be very challenging as there are many keys created on the fly/at row creation, so for activities >> resources, we'd have to insert the activity, grab or remember the activity key, then add resources with that newly created activity key.
Each WPackage (this is the main parent table) could have one or more of the following child table entries:
1+ activities
each activity would have 1+ resource
1+ budgets
1+ Signatures
1+ CostCodes
Our schema or model diagram would be:
WPackage
--Activities
-----Resources (child of Activities)
--CostCodes
--Budgets
--Signatures
The following code fails on:
dbContextTransaction.Commit();
with an error:
The transaction operation cannot be performed because there are pending requests working on this transaction.
[HttpPost]
public ActionResult Copy([Bind(Include = "ID,WBSID,...***fields excluded for brevity")] Package model)
{
if (ModelState.IsValid)
{
try
{
using (var dbContextTransaction = db.Database.BeginTransaction())
{
var dbCList = db.Packages.Join(db.WBS,
*expression omitted for brevity*)
// this dbClist will build about 18 items in the collection for below loop
foreach (var dbitem in dbCList)
{
int testWPID = dbitem;
WPackage prvWP = db.WPackages.Find(dbitem);
int previousWPID = dbitem;
WPackage previousWP = db.WPackages.Find(dbitem);
model.ID = dbitem;
db.WPackages.Add(model);
db.SaveChanges();
var budgets = db.Budgets.Where(i => i.WPID == previousWPID);
foreach (Budget budget in budgets)
{
budget.WPID = model.ID;
db.Budgets.Add(budget);
}
var costCodes = db.CostCodes.Where(i => i.WPID == previousWPID);
foreach (CostCode costCode in costCodes)
{
costCode.WPID = model.ID;
db.CostCodes.Add(costCode);
}
var activities = db.Activities.Where(i => i.WPID == previousWPID);
// *code excluded for brevity*
var previousActivityID = activity.ID;
db.Activities.Add(activity);
db.SaveChanges();
var resources = db.Resources.Where(i => i.ActivityID == previousActivityID);
foreach (Resource resource in resources)
{
resource.WPID = model.ID;
resource.ActivityID = activity.ID;
resource.ActivityNumber = activity.ActivityNumber;
db.Resources.Add(resource);
db.SaveChanges();
}
}
var signatures = db.RolesAndSigs
.Where(i => i.KeyId == previousWPID && i.Type == "WPL")
.OrderBy(i => i.Role)
.OrderBy(i => i.Person);
foreach (RolesAndSig signature in signatures)
{
db.RolesAndSigss.Add(signature);
}
db.SaveChanges();
dbContextTransaction.Commit();
}
}
}
}
I've also tried to have the Commit() run outside the foreach dbitem loop like:
db.SaveChanges();
//dbContextTransaction.Commit();
}
dbContextTransaction.Commit();
...but this returns error of:
[EXCEPTION] The property 'ID' is part of the object's key information and cannot be modified.
The code you posted has some issues that don't make sense, and probably aren't doing what you think they are doing. The crux of the issue you are facing is that Entity Framework tracks all references to entities it loads and associates:
Firstly this code:
int testWPID = dbitem;
WPackage prvWP = db.WPackages.Find(dbitem);
int previousWPID = dbitem;
WPackage previousWP = db.WPackages.Find(dbitem);
prvWP and previousWP will be pointing to the exact same reference, not two copies of the same entity. Be careful when updating either or any other reference retrieved or associated with that same ID. They all point to the same instance. If you do want a stand-alone snaphot reference you can use AsNoTracking().
Next, when you do something like this in a loop:
model.ID = dbitem;
db.WPackages.Add(model);
In the first iteration, "model" is not an entity. It is a deserialized block of data with the Type of the Package entity. As soon as you call .Add(model) that reference will now be pointing to a newly tracked entity reference. In the next loop you are telling EF to change that tracked entity reference's ID to a new value, and that is illegal.
What it looks like you want to do is create a copy of this model for each of the 18 expected iterations. For that what you want to do would be something more like:
foreach (var dbitem in dbCList)
{
var newModel = new WPackage
{
ID = dbItem,
WBSID = model.WBSID,
/// copy across all relevant fields from the passed in model.
};
db.WPackages.Add(newModel);
// ...
}
It would be quite worthwhile to leverage navigation properties for the related entities rather than using explicit joins and trying to scope everything in an explicit transaction with multiple SaveChanges() calls. EF can manage all of the FKs automatically rather than essentially using it as a wrapper for individual ADO CRUD operations.
You will need to be explicit between when you want to "clone" an object reference vs. "copy" a reference. For example, if I have a Customer that has an Address, and Addresses have a Country reference, when I clone a Customer, I will want to clone a new Address record for that Customer, however ensure that the Country reference is copied across. If I have a record for Jack at an 123 Apple Street, London in England, and go to clone Jack to make a record for Jill at the same address, they might be at the same location now, but not always, so I want them to point at different Address records in case Jill moves out. Still, there should only be one record for "England". (Jill may move to a different country, but her address record would just point at a different Country Id)
Wrong:
var jill = context.Customers.Single(c => c.Name == "Jack");
jill.Name = "Jill";
context.Customers.Add(jill);
This would attempt to rename Jack into Jill, then "Add" the already tracked instance, resulting in an exception.
Will work, but still Wrong:
var jack = context.Customers.AsNoTracking().Single(c => c.Name == "Jack");
var jill = jack;
jill.Name = "Jill";
context.Customers.Add(jill);
This would technically work by loading Jack as an untracked entity, and would save Jill as a new record with a new Id. However this is potentially very confusing. Depending on how the AddressId/Address is referenced we could end up with Jack and Jill referencing the same single Address record. Bad if you want Jack and Jill to have different addresses.
Right:
var jack = context.Customers
.Include(c => c.Address)
.ThenInclude(a => a.Country)
.Single(c => c.Name == "Jack");
var jill = new Customer
{
Name = "Jill",
// copy other fields...
Address = new Address
{
StreetNumber = jack.Address.StreetNumber,
StreetName = jack.Address.StreetName,
Country = jack.Address.Country
}
};
context.Customers.Add(jill);
The first detail is to ensure when we load Jack that we eager load all of the related details we will want to clone or copy references to. We then create a new instance for Jill, copying the values from Jack, including setting up a new Address record. The Country reference is copied across as there should only be ever a single record for "England".
Edit: For something like a roll-over scenario if you have a package by year, let's use the example of a Package class below:
public class Package
{
[Key]
public int PackageId { get; set; }
[ForeignKey("PackageType")]
public int PackageTypeId { get; set; }
public int Year { get; set; }
// .. More package related details and relationships...
public virtual PackageType PackageType { get; set; }
}
A goal might be to make a new Package and related data for Year 2022 from the data from 2021, and apply any changes from a view model passed in.
Find is a poor choice for this because Find wants to locate data by PK. If you're method simply passes an entity to be copied from (I.e. the data from 2021) then this can work, however if you have modified that data from 2021 to represent values you want for 2022 that could be dangerous or misleading within the code. (We don't want to update 2021's data, we want to create a new record set for 2022) To make a new Package for 2022 we just need the updated data to make up that new item, and a way to identify a source for what to use as a template. That identification could be the PK of the row to copy from (ProductId), or derived from the data passed in. (ProductTypeId, and Year-1) In both cases if we want to consider related data with the "copy from" product then it would be prudent to eager load that related data in one query rather than going back to the database repeatedly. Find cannot accommodate that.
For instance if I want to pass data to make a new product I pass a ProductTypeId, and a Year along with any values to use for the new structure. I can attempt to get a copy of the existing year to use as a template via:
var existingProduct = context.Products
.Include(x => x.Activities) // Eager load related data.
.Include(x => x.CostCodes)
// ...
.Single(x => x.ProductTypeId == productTypeId && x.Year = year - 1);
or if I passed a ProductId: (such as if I could choose to copy the data from a selected year like 2020 instead)
var existingProduct = context.Products
.Include(x => x.Activities)
.Include(x => x.CostCodes)
// ...
.Single(x => x.ProductId == copyFromProductId);
Both of these examples expect to find one, and only one existing product. If the request comes in with values that it cannot find a row for, there would be an exception which should be handled. This would fetch all of the existing product information that we can copy from, alongside any data that was passed into the method to create a new Product.

Adding course to DB (LINQ)

I am trying to add a new record to a table i my database. The code I have adds the record to the table but also throws an exception "System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK_COURSE_TAKEN'. Cannot insert duplicate key in object 'dbo.COURSE_TAKEN'. The duplicate key value is (000160228, HIS 155).". Why is it trying to add the record twice? I know for 100% certainty that there is no existing record already present before I try to add it. Below is my code for adding the record:
using (KuPlanEntities db = new KuPlanEntities())
{
var courseTake = new COURSE_TAKEN();
courseTake.Id = "000160228";
courseTake.courseAbNum = courseAbNum;
courseTake.status = status;
courseTake.grade = grade;
db.COURSE_TAKEN.Add(courseTake);
db.SaveChanges();
}

MVC Enity Framework get KEY attribute from table

I am trying to extract the [key] value from a table.
This is for a logging method which looks like this:
private List<Log> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userId)
{
List<Log> result = new List<Log>();
DateTime changeTime = DateTime.Now;
// Get the Table() attribute, if one exists
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
// Get primary key value
string keyName = dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name;
if (dbEntry.State == EntityState.Added)
{
result.Add(new Log()
{
LogID = Guid.NewGuid(),
EventType = "A", // Added
TableName = tableName,
RecordID = dbEntry.CurrentValues.GetValue<object>(keyName).ToString(),
ColumnName = "*ALL",
NewValue = (dbEntry.CurrentValues.ToObject() is IDescribableEntity) ? (dbEntry.CurrentValues.ToObject() as IDescribableEntity).Describe() : dbEntry.CurrentValues.ToObject().ToString(),
Created_by = userId,
Created_date = changeTime
}
);
}
The problem is to get the RecordID when a Record is added, when it get deleted or modified it works. (The code to get it is the same)
When I debug I also see that it has the KeyAttribute in the CustomAttributes base but not sure why it always shows up as 0.
I can debug more if needed
After savechanges you can fetch the newly created key. (Guess the key is generated automatically inserting a new record).
for me you have several solutions.
first solution:
enlist added entity from the context
SaveChanges
enumerate the enlisted entities to add log
SaveChanges
the problem (or not) here is that the business and the logging are not in the same transaction.
antother problem, depending on the implementation, is to prevent loging of log of log of log... This can be donne by filtering entities by typeName for example.
other solution:
add and ICollection<Log> to your entities.
the problem here is to unify the logs:
inheritance of entity, or
several log tables + a view
...
other solution
use trigger at database level
other solution
..., use cdc if you have Sql Server Enterprise Edition

Multiple Databases in ASP.NET MVC

I'm very first experience in ASP.NET MVC and after I read through a few articles I decided I try my new project with MVC.
And I used ADO.net entity data model and I created Create/ Delete/ Details/ Edit/ Index. It works fine.
So I plan to improve in user interface because some of the fields are comes from another databases such as HR Database, etc... for employee information.
for e.g : to choose Employee's Name on my form, I have to use DropDownList and that data comes from another database, HR as I mentioned in above. I have no idea how to access difference database in one model and here I asked How to solve multiple databases in one edmx for ASP.net MVC?
However I tried to create one more model for the other database and try to join with Linq.
//
// GET: /ARS/
public ActionResult Index()
{
var employee = new List<EMPLOYEE>(chr.EMPLOYEEs);
var reqform = new List<RequestForm>(ars.RequestForms.Include(r => r.Genre));
var requestforms = from r in reqform
join e in employee on r.PrjDeptMgr equals e.EmployeeID
select new
{
r.FormID,
r.GenreID,
r.JobNo,
r.Description,
r.StartDate,
r.EndDate,
r.PrjDeptMgr,
r.IsAgreed,
r.Genre
};
//var requestforms = ars.RequestForms.Include(r => r.Genre);
return View(requestforms.ToList());
}
But I got this error
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[<>f__AnonymousType29[System.Int32,System.Int32,System.String,System.String,System.DateTime,System.DateTime,System.String,System.Boolean,Access_Request_System.Models.Genre]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Access_Request_System.Models.RequestForm]'.
Really no idea for this case... Please!
Your view file requires IEnumerable<RequestForm>, but the type you're passing does not match the required type (it's anonymous).
Try the following changes.
List<RequestForm> requestforms = (from r in reqform
join e in employee on r.PrjDeptMgr equals e.EmployeeID
select new RequestForm
{
FormID = r.FormID,
Genre = r.GenreID,
JobNo = r.JobNo,
Description = r.Description,
StartDate = r.StartDate,
EndDate = r.EndDate,
PrjDeptMgr = r.PrjDeptMgr,
IsAgreed = r.IsAgreed,
Genre = r.Genre
}).ToList();
return View(requestForms);

where is bidirectional ? grails one-to-one, testing bidirectional (continue)

class Book {
String title
Date releaseDate
String ISBN
static belongsTo = [person:Person] // it makes relationship bi-directional regarding the grails-docs
}
class Person {
Book book; // it will create person.book_id
String name
Integer age
Date lastVisit
static constraints = {
book unique: true // "one-to-one". Without that = "Many-to-one".
}
}
There is a test which test if it is real bidirectional or not. As i understand it.
public void testBidirectional() {
def person = new Person(name:"person_c1", age: 99, lastVisit: new Date())
def book = new Book(
title:"somebook_c1",
ISBN: "somebook_c1",
releaseDate: new Date()
)
person.setBook (book)
assertNotNull(person.save())
def bookId = person.getBook().id
Book thatBook = Book.get(bookId)
assertNotNull(thatBook.person) // NULL !!!
}
So, i save a person with a book, and then i got that book from db by id. Then from that book i try to get back the person which book should refer to (because it should be bidirectional, right?). Eventually i got null instead of an instance of the person.
The questing is: how to make that test working?
i have found the solution how to get it working, but still can not understand why it does not work without 'refresh', see below:
public void testBidirectional() {
def person = new Person(name:"person_c1", age: 99, lastVisit: new Date())
def book = new Book(
title:"somebook_c1",
ISBN: "somebook_c1",
releaseDate: new Date()
)
person.setBook (book)
def p = person.save()
assertNotNull p
person.refresh() //load the object again from the database so all the changes made to object will be reverted
//person = Person.get(p.id) // BUT this also gets the object from db ...?
def bookId = person.getBook().id
assertNotNull bookId
def thatBook = Book.get(bookId)
assertNotNull(thatBook.person)
}
So, here as you can see i use 'refresh' to get it working, but why it does not work without 'refresh' but with the following line after 'refresh' - this one:
person = Person.get(p.id) // BUT this also gets the object from db ...?
If i just want to get object from database by id, then it would be without bidirectional?
Your problem is probably caused by the way that Hibernate works. Grails used Hibernate under the hood.
Even when you call "save", the object person may (and usually) not saved in database. That's because Hibernate is programmed to optimize the query, so it often waits to perform all query at then end of the Hibernate session.
That means if you don't call "refresh", the book-person relation (person.setBook) is still in memory, but not saved in database. Hence you can't get the book.person from book.
To enforce the save, you can use "refresh" like the previous answer, or use flush:true.
I still not try, but it's very likely that you will produce desired results with:
person.save(flush:true)

Resources