Invalid object graph in Linq to SQL - linq-to-sql

I have a GiftCards table in my DBML that has a related property called Audit. The Audits are stored in a separate table. Each Audit has a related Person associated to it. There is also a Persons table. The relationships are set up and are valid in my DBML.
The problem is that when I instantiate a new Gift Card I also create a new related Audit in the OnCreated() method. But at the same time, I also create a related Person when I instantiate a new Audit. The Person is the current user. Actually the Audit's OnCreated method checks if the user already exists.
The problem is that when I instantiate a new gift Card, it also creates an associated Audit, which is fine, and the Audit creates an associated Person. But the Person already exists in the database. When I look at the data context's GetChangeSet(), it shows 3 inserts. The Persion should not show as an insert because he already exists in the database.
Here is how I implemented this. It is an MVC application where the Controller receives a gift card:
[HttpPost]
public ActionResult Save(GiftCardViewModel giftCard)
{
if (ModelState.IsValid)
{
GiftCard gc = GiftCardViewModel.Build(giftCard);
repository.InsertOrUpdate(gc);
repository.Save();
return View("Consult", new GiftCardViewModel(repository.Find(gc.GiftCardID)));
}
else
SetupContext();
return View("_Form", giftCard);
}
The Gift Card has:
partial class GiftCard
{
partial void OnCreated()
{
// Set up default audit.
this.Audit = new Audit();
}
}
The Audit class has:
partial void OnCreated()
{
// Setup timestamp
this.Timestamp = DateTime.Now;
this.Person = Person.GetPerson(Membership.GetUser().UserName);
}
And finally, my Person class has:
public static Person GetPerson(String username)
{
using (GiftCardDBDataContext database = new GiftCardDBDataContext())
{
// Try to get the person from database
Person person = database.Persons.SingleOrDefault(personData => SqlMethods.Like(personData.Username, username));
if (person == null)
{
person = new Person()
{
Username = username,
FullName = "Full name TBD"
};
database.Persons.InsertOnSubmit(person);
database.SubmitChanges();
}
// Return person data
return person;
}
}
When I create a new gift card, I always get an error saying that it's attempting to insert a duplicate person in the Persons table. I don't understand because my static class specifically checks if the Person already exists, if yes, I return the Person and I don't create a new one. Yet, the GetChangeSet() shows three inserts including the Person, which is wrong.
What am I doing wrong here?

I believe your issue here is that you're using multiple contexts. You have one being created by your repository, and another is created in the static method on your Person object. You also aren't making any effort to attach the Person created/retrieved from the other context to the context of your Audit class.
You should look at a single unit of work, a single DataContext class, and perform all your work in that.

Related

Selecting a value from an n:n relationship

I currently have three tables: users, roles, and a user_to_role “pivot” table defining a many-to-many relationship between users and roles:
users
protected $fillable = [
'name', 'email', 'password',
];
user_to_role
protected $fillable = [
'id', 'user_id', 'role_id'
];
roles
protected $fillable = [
'id', 'role_name',
];
The role_name values are admin and client.
When a user logs in, I want to show a view for the specific role that the user is assigned. I don't really know how to do that in the controller, however. I have something like the following, but I know it won’t work:
public function index()
{
if (Auth::user()->role_id==1) {
// and something here which I don't know
return view('homeadmin');
}
}
I know I have to take the id from the roles table, make the connection with the user_to_role pivot, and then join that with the users table, but I don't really know how.
You need to define a relationship between User model and Role model.
# User.php
public function roles()
{
return $this->belongsToMany(Role::class, 'user_to_role');
}
Optionally, define the relationship on Role model as well.
# Role.php
public function users()
{
return $this->belongsToMany(User::class, 'user_to_role');
}
Then, you can access the relationship and use collection methods on it.
public function index()
{
// or Auth::user()->roles->contains('role_name', 'admin') if you want to be specific
if (Auth::user()->roles->contains('id', 1)) {
return view('homeadmin');
}
return view('homeuser');
}
Optionally, you could make a method in the User model to check if an user is admin or client.
# User.php
public function isAdmin()
{
return $this->roles->contains('id', 1); // or contains('role_name', 'admin')
}
public function isClient()
{
return $this->roles->contains('id', 2); // or contains('role_name', 'client')
}
public function index()
{
if (Auth::user()->isAdmin()) {
return view('homeadmin');
}
return view('homeclient');
}
Eloquent Relationships - Many to Many
Collections - contains() method
First of all, if you have User and Role Model mapping to your users and roles table, the convention is to name your pivot table role_user. But you can get along with your current table naming as well.
I would agree the answer of IGP and add a few more suggestions.
If you just need to implement role and user and don't have to build it yourself, there are plenty of existing packages that can help you handle role-permission. You don't really needs to build from scratch. For example, depends on the Laravel version you use, you may choose;
spatie/laravel-permission
Bouncer
Zizaco/Entrust
If you would like to implement role management yourself, when you define your relationship, you need to think about if a user would have multiple roles in the future. Based on what you show us right now, there are only client and admin role. Looks like a user would only be either client or admin but not both. And if you are sure those are the only two roles and a user would be either one, you don't need to have roles table at all. You can just add a boolean column such as is_admin in users table to flag the role.
But let's say you will have more roles, and a user can have multiple roles at the same time. Then you DO need to define a many to many relationship. Other answers already provide example on that pretty well. I would also suggest to define a universal role-handling model function to check all roles. In your User.php model,
public function hasRole($role)
{
// check if user have any of the specified roles
if (is_array($role)) {
foreach($role as $r) {
if ($this->roles->contains('role_name', $r)) {
return true;
}
}
return false;
} else {
return $this->roles->contains('role_name', $role);
}
}
That way, in anywhere in your App, you can check your user role by calling
Auth::user()->hasRole('admin');
or check if user contains any role in a list by calling
Auth::user()->hasRole(['admin', 'client']);

linq to sql insert failed due to FK constraint, how can I submit all related objects at the same time?

I am using linq to sql and trying to insert new objects. Here's an example of my code:
public class Farm(){
public List<FarmAnimals> FarmAnimals ();
public string FarmName;
}
Public class FarmAnimal(){
public string name;
}
public void Insert(FarmModel farm)
{
using (var context = new FarmDataClassesDataContext())
{
context.Farms.InsertOnSubmit(new Farm { FarmName = farm.FarmName });
foreach (var animal in farm.FarmAnimals)
{
context.Responses.InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = farm.Id });
}
context.SubmitChanges();
}
}
I get a FK constraint error when it tries to insert a farm animal, referencing the farmID (which equals 0). Since the farm hasn't been inserted yet, it doesn't have an ID for the farmanimals to refer to. How do I get the farm submitted so that the farm animals FK can be properly set?
Thanks,
The problem is you are thinking SQL way, and not ORM way.
The SQL way assigns a foreign key:
InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = farm.Id });
The ORM way assigns entities. Notice the part between ** ** in the following code sample.
var myFarm = new Farm { FarmName = farm.FarmName };
Con...InsertOnSubmit(myFarm)
Con...InsertOnSubmit(new FarmAnimal {name = animal.name, **farm = myFarm**});
Because you assign the entity, proper insertions will be handled and as a bonus in one transaction.
You have to submitChanges before inserting the FarmAnimals, and you need to have the column auto creating the key with autoincrement. Also make sure that the column in the table object in the DBML-file auto updated on insert.
public class Farm(){
public List<FarmAnimals> FarmAnimals ();
public string FarmName;
}
Public class FarmAnimal(){
public string name;
}
public void Insert(FarmModel farm)
{
using (var context = new FarmDataClassesDataContext())
{
Farm newFarm = new Farm { FarmName = farm.FarmName }; <--- New
context.Farms.InsertOnSubmit(newFarm); <---Edited
context.SubmitChanges(); <--- New
foreach (var animal in farm.FarmAnimals)
{
context.Responses.InsertOnSubmit(new FarmAnimal {name = animal.name, farmID = newFarm.Id }); <--- Edited
}
context.SubmitChanges();
}
}
To expand on Pleun's answer: You need to assign entities rather than IDs. The property that you're trying to assign to is mapped to a column with a foreign-key constraint, so it won't work for assigning an entity--to do that you instead need a property that maps to the relationship between two tables. How you do that varies by the tool you're using.
For the purposes of this explanation, I'll assume that you have a Farm table with a primary-key column called ID and another column called Name; and a FarmAnimal table with a foreign-key column named FarmFK that points to the Farm table and another column called Name.
Based on the DataContext part of the name I assume you're using the O/R Designer tool built in to Visual Studio, right? If so, go to the O/R Designer by opening your dbml file, select the association (represented as an arrow) between Farm and FarmAnimal (if there's not already an arrow, select the Association tool from the Toolbox and drag from Farm to FarmAnimal), and view the association's properties. You'll see properties called "Child Property" and "Parent Property". (The parent table is the table with the primary key in the relationship.) Expand those to see the "Name" sub-property of each. Those are the property names you'd use in code to access the two ends of the relationship. Typically they have poorly-chosen names based on the automatic generation, so rename them as needed. In this case let's rename the parent property's name to Animals and the child property's name to 'Farm'. You'd then be able to do the following in your code:
public void Insert(FarmModel farmModel)
{
using (var context = new FarmDataClassesDataContext())
{
var farm = new Farm
{
Name = farmModel.FarmName
};
context.Farms.InsertOnSubmit(farm);
foreach (var animalModel in farmModel.FarmAnimals)
{
var critter = new FarmAnimal
{
Name = animalModel.name,
Farm = farm
}
context.Responses.InsertOnSubmit(critter);
}
context.SubmitChanges();
}
}
Does that answer your need?

LINQ To SQL does not work when adding new object

I use the following code to insert a new record to my Users table:
public bool CreateUser(User obj)
{
obj.Id = Guid.NewGuid();
using (_db = new CMSDataContext())
{
obj.SiteId = SiteID;
_db.Users.InsertOnSubmit(obj);
_db.SubmitChanges();
}
return true;
}
I do not get any errors, and everything seems fine. I can read a record from database with same DataContext. But after the above method runs completely, I see nothing new in my Users table. Why?
Is the id column truly a PK in the sql server database?

LINQ to SQL SubmitChanges() Inserts two Database Rows and one Child Row

I have this going me crazy,
I'm attaching a List with 1 Customer and 1 Address child record row.
Everything seems OK while debugging. 1 customer Row and 1 Address Row should inserted.
But instead I get 2 Customer Records and 1 Address Row.
I don't know why. When Attaching and looping inside the List only 1 record seen.
Any points?
[EDITED]
Code Attached:
public bool InsertUpdateCustomers(List<Customer> customerList, List<Customer> originalCustomers)
{
using (DbContext db = new DbContext(DbContext.ConnectionString))
{
db.Log = Console.Out;
List<Customer> customerCloned = new List<Customer>();
customerList.ForEach(p => customerCloned.Add(p.CloneObjectGraph()));
customerCloned.ForEach(p => p.Address =
customerList.Where(pe => pe.Id == p.Id).Single().Address.CloneObjectGraph());
customerCloned.ForEach(p =>
{
if (p.Id > 0)
{
db.Customer.Attach(p,
originalCustomers.Single(
x => x.Id == p.Id));
db.Address.Attach(p.Address,
originalCustomers.Single(
x => p.AddressId== x.AddressId).
Address);
}
});
customerCloned.ForEach(p =>
{
if (p.Id == 0)
db.Customer.InsertOnSubmit(p);
});
try
{
db.SubmitChanges(ConflictMode.ContinueOnConflict);
return true;
}
catch (Exception ex)
{
return false;
}
}
}
I have checked the Log in the output and I see indeed 2 Inserts in the table.
I don't see nothing about the Address, but inserts correctly.
It could be the foreign key problem i don't get it.
I guess you've solved this for now but I ran into a similar issue and wanted to report back my understanding of this issue for future users.
The issue, I believe, is that you are using an existing list of Customer objects retrieved from the DB using a particular DataContext. You are then creating a new DataContext in your method and with this new DataContext, you are attaching an Address object.
This Address object (assuming has a foreign key relation with Customer) creates a new Customer object in the DB since the DataContext for which SubmitChanges is called, the originalCustomer is also treated as a new record.
In other words, to avoid these problems, you must re-use the existing DataContext using which the originalCustomer List was fetched so that inserting the child record of Address doesn't trigger an entry into the parent table.
Hope this helps.

What's the best way to save a one-to-many relationship in Linq2Sql?

I'm trying to figure out the best way to save a simple one-to-many relationship in Linq2Sql.
Lets assume we have the following POCO model (pseduo code btw):
Person has zero to many Vechicles.
class Person
{
IList<Vehicle> Vehicle;
}
class Vehicle
{
string Name;
string Colour;
}
Now, when i save a Person, i pass that poco object to the repository code (which happens to be L2S). I can save the person object fine. I usually do this.
using (Db db = new Db())
{
var newPerson = db.People.SingleOrDefault(p => p.Id == person.Id) ?? new SqlContext.Person();
// Left to right stuff.
newPerson.Name = person.Name;
newPerson.Age = person.Age;
if (newPerson.Id <= 0)
db.People.InsertOnSubmit(newPerson);
db.SubmitChanges();
}
i'm not sure where and how i should handle the list of vehicles the person might have? any suggestions?
using (Db db = new Db())
{
var newPerson = db.People.SingleOrDefault(p => p.Id == person.Id) ?? new SqlContext.Person();
// Left to right stuff.
newPerson.Name = person.Name;
newPerson.Age = person.Age;
// add vehicles.
Vehicle firstV = new Vehicle();
firstV.Name = "some name";
firstV.Person = newPerson; // need to do this to set the person Id on the vehicle.
newPerson.Vehicle.Add(firstV);
// now when you save the Person it should save the Vehicle list
// if you set Cascade save update on the list. (not sure how to do that in L2S
if (newPerson.Id <= 0)
db.People.InsertOnSubmit(newPerson);
db.SubmitChanges();
}
Now you may choose to construct the list of vehicles at another level , with the data that's coming from the interface.
But you need to remember that it's not enough to add the Vehicle to the list on the Person object , you also need to set the vehicles Person property to the person that has the vehicles.
Observation I'm not sure about this but when you do db.People.SingleOrDefault you might be loading the whole People table in memory . That's not something you want to do. Corrected by Slace in the comments.
All you need to do is ensure that there are the appropriate relationships set up within the database.
If your Vehicle table has a PersonId and there is a foreign key between them when you add them to the DBML Linq to SQL will detect that there is a relationship between them and create a Table<T> representation of the relationship.