How to save class with master/detail - c#

This is my class Record which can be master/detail/details
Records of teachers and their students
class Record
{
public string RecorNo { get; set; }
private List<Teacher> _Student = new List<Teacher>();
public List<Teacher> Teacher
{
get { return _Teacher; }
set { _Teacher=value; }
}
}
class Teacher
{
private string _TeacherName;
public Teacher(string teachername)
{
_TeacherName = teachername;
}
public string TeacherName
{
get { return _TeacherName; }
set { _TeacherName = value; }
}
private List<Students> _students = new List<Students>();
public List<Students> Students
{
get { return _students;}
set { _students = value;}
}
}
class Details
{
private string _studentname;
public Details(string studentsname)
{
_studentsname = studentsname;
}
I have bind the Teacher/Student in 2 datagridview like so:
TeacherBindingSource.DataSource = record.Teacher;
StudentBindingSourcce.DataSource = TeacherBindingSource;
StudentBindingSourcce.DataMember = "Student";
dataGridView1.DataSource =TeacherBindingSource;
dataGridView2.DataSource = StudentBindingSourcce;
Now Im lost how to save this record.
How can I save this to the database?
I want to have a.
record.Save(); method that will save the whole object.
I can iterate through the list and insert it one by one but the record may be
existing already in the database so i had to know which record is added, updated or deleted?
Also how to make a method that will fill the teacher/student list like:
records.LoadTeachers();
records.Teacher("Smith").LoadStudents();

you might want to have a look at ORM (object-relational mapping) libraries like Microsofts Entity Framework or (my favorite) NHibernate. Their sole reason to exist is to make persisting objects to a relational database easier. They can create new rows, can track object updates and relations between objects. You could create the logic needed to persist your objects on your own, but the established ORM libraries are matured, widely used and probably much better designed than anything a single developer could come up with in a reasonable amount of time.

Related

Updating object with child collection using Entity Framework causing duplicates in database

I have a Customer class that has a relationship to an Address class:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street1 { get; set; }
//Snip a bunch of properties
public virtual Customer Customer { get; set; }
}
I have an edit form which displays all the fields for both the customer and address. When this form is submitted, it calls the Edit method in the controller:
public ActionResult Save(Customer customer)
{
if (!ModelState.IsValid)
{
var viewModel = new CustomerFormViewModel
{
Customer = customer,
CustomerTypes = _context.CustomerTypes.ToList()
};
return View("CustomerForm", viewModel);
}
if (customer.Id == 0)
_context.Customers.Add(customer);
else
{
var existingCustomer = _context.Customers
.Include(c => c.Addresses)
.Single(c => c.Id == customer.Id);
existingCustomer.Name = customer.Name;
existingCustomer.TaxId = customer.TaxId;
existingCustomer.CustomerTypeId = customer.CustomerTypeId;
existingCustomer.CreditLimit = customer.CreditLimit;
existingCustomer.Exempt = customer.Exempt;
existingCustomer.Addresses = customer.Addresses;
}
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
This doesn't work and creates duplicate entries in the Addresses table in the DB. I think I understand why (EF isn't smart enough to know the Addresses inside the collection need to be added/modified/deleted as the case may be). So, what is the best way to fix this?
My instinct is that I need to iterate over the Addresses collections and compare them manually, adding any new ones from the form that don't exist for the customer, updating ones that do exist, and deleting ones that were not sent by the form but exist in the DB for the customer. Something like (ignoring the delete functionality for now):
foreach(Address address in customer.Addresses)
{
if (address.Id == 0)
// Add record
else
// Fetch address record from DB
// Update data
}
// Save context
Is this the best way to go about this, or are there any EF tricks to iterating and syncing a child collection to the DB?
Oh, and one question which has me scratching my head - I can sort of understand how a new address record is getting created in the DB, but what I don't get is the existing address record is also updated to have its customer_id set to NULL...how the heck does that happen? That leads me to believe that EF does see the original address record is somehow linked (as it is modifying it) but it's not smart enough to realize the record I'm passing in should replace it?
Thanks -- also, this is EF6 and MVC5
The problem comes from the line
existingCustomer.Addresses = customer.Addresses;
in your code. This like assigns field Addresses from customer coming from the model. So far ok. The point is that customer does not have any relation to the database model at this point (it's not coming from the database but from the view).
If you would like to update existingCustomer.Addresses with the data coming from the model, you need to merge the data instead of replacing it. The following "pseudo code" might give you a direction:
void MergeAddresses(var existingAddresses, var newAddresses) {
foreach(var address in newAddresses) {
if (existingAddresses.Contains(newAddress)) {
// merge fields if applicable
}
else {
// add field to existingAddresses - be ware to use a "cloned" list
}
}
// now delete items from existing list
foreach (var address in existingAddresses.CloneList()) {
if (!newAddresses.Contains(address)) {
// remove from existingAddresses
}
}
}
Is this the best way to go about this, or are there any EF tricks to iterating and syncing a child collection to the DB?
No, there aren't such tricks. EF designers left saving detached entities totally up to us - the developers.
However there is a package called GraphDiff which is addressing that, so you could give it a try. Here is how your code would look like using it:
using RefactorThis.GraphDiff;
...
_context.UpdateGraph(customer, map => map.OwnedCollection(
e => e.Addresses, with => with.AssociatedEntity(e => e.Customer)));
_context.SaveChanges();

How to return the ID of each record after insert using EF

I have a data class with nested Lists. A simplified example (not real code):
public class Movie
{
public Int32 TVIndex;
public string MovieName;
public string MovieRating;
public string MovieRuntime;
public List<Actor> MovieActors;
public List<MovieArt> MovieImages;
}
public class Actor
{
public string ActorName;
public string ActorRole;
}
public class MovieArt
{
public string ImagePath;
}
List<Movie> myMovies = new List<Movie>();
This may contain many movies and each movie may contain many Actors and many MovieArt.
I pass this list to a web service that inserts the records into tables using DataEntityModel.
Something Like..
public void InsertMovies(List<Movie> request)
{
using (TransactionScope scope = new TransactionScope())
{
using (MyMovieStorageEntities DbContext = new MyMovieStorageEntities())
{
foreach (Movie m in request)
{
Movies movie = new Movies();
movie.MovieName = m.MovieName;
movie.MovieRating = m.MovieRating;
movie.MovieRuntime = m.MovieRuntime;
DbContext.DBMovies.Add(movie);
foreach (Actor a in m.MovieActors)
{
Actors actor = new Actors();
actor.ActorName = a.ActorName;
actor.ActorRole = a.ActorRole;
DbContext.DBActors.Add(actor);
}
for (MovieArt i in m.MovieImages)
{
MovieArt artwork = new MovieArt();
artwork.FileName = i.FileName;
DbContext.DBMovieArt.Add(artwork);
}
}
DbContext.SaveChanges();
}
}
}
In the Movie table is an Identity column and I need to return the identity for each inserted record for use in the client app.
I am new to DataEntity and not really sure where to start. All the searching I have done hasn't really helped. Any pointers?
Don't lose the references to your entity framework objects (e.g. add them to a list), and after SaveChanges(), the object property containing the primary key will be automatically updated with the ID.
See here for more info
How can I get Id of inserted entity in Entity framework?
EF automatically returns the ID. movie.ID gives you the last inserted ID you can use it or return it from your method to use somewhere else.

Entity Framework & Multitables

I have a linq to entity expression:
entities = new zdmEntities();
var reltables = (from r in entities.relations
orderby r.id
select new Relation
{
Id = r.id,
Devices = r.devices.device_name,
Systems = r.systems.system_name,
Models = r.models.name,
Functions = r.functions.function_name
}).ToList();
ultraGrid1.DataSource = reltables.ToList();
class Relation
{
public int Id { get; set; }
public string Devices { get; set; }
public string Systems { get; set; }
public string Models { get; set; }
public string Functions { get; set; }
}
As you can see the relation table contains a link to other tables.
The class Relation contains my columns for the datagrid.
But there is one problem... can't be posssible two way databinding between grid and database. I wrote all the updates manually but it's very difficult.
I understand that this is because in linq expression there is 'new'. But how do you make it without 'new'?
How I can display columns that I need with a two-way databinding and without own class like 'Relation'.
Windows Form. Not wpf)
Thanx, Alex.
When you write entities.Relations.Select(r => new ...) you are making a projection of each Relation EF object into a new non-EF object. By EF object I mean a class which is known by and tracked by EntityFramework.
Making changes to a EF-known class instance would propagate the changes back to DB when you save changes in your db/entity context. In contrast, making changes to a EF-unknown projection (or any projection) has no effect on the original object.
There are two ways you can achive what you want: If your DataGrid (NetAdvantage UltraGrid?) supports binding to subobjects (such as relation.device) you can then use ultraGrid.DataSource = entities.relations and define grid columns to bind to field devices.device_name. The other way would be something like this:
class Relation
{
private readonly EfRelation _originalRelation;
public Relation(EfRelation originalRelation)
{
this._originalRelation = originalRelation;
}
public string Devices
{
get { return this._originalRelation.devices.device_name; }
set { this._originalRelation.devices.device_name = value; }
}
// Repeat for other properties
}
...
var reltables = entities.relations.ToList().Select(r => new Relation(r)).ToList();
Then you just save changes to your db/object context. The EfRelation is the name of your EF Relation class, change it to the name of your EF class which represents a relation.

C# Object Oriented Programming, Inheritance, Foreign Keys and DB

the title might be too generic, but I have a very specific question about how to design C# classes based on a relational database.
Let's say we have a table called TPerson and a table called TComment, both tables have one column in common (PERSON_ID, PK on TPerson and FK in TComment).
Let's say we have a web app where we are displaying a list showing all comments from everyone, in this list we are showing the comment and the first name and last name of the author (TPerson) and the date the comment was created as well. I think it is not appropriate to use inheritance (Base class TPerson, derived class TComment) because I don't need the alias for example, I don't want to drag the other columns with me if I only need first name and last name (column TPerson might have lots of columns).
I want a class design that is able to:
Add Person objects and save to DB
Add Comment objects and save to DB
Retrieve the list of comments with first name and last name of the person
Is there a way to create re-usable code by only retrieving or using parts of an object ?
The only way to do this would be to use inheritance and every time I retrieve a Comment, I would also create the Person object that goes with it, but in some parts it would be overkill to retrieve the entire thing when I only need certain parts of it...
If I were to create classes to represent the data, I would go with something like this, Any ideas ?, thanks for your help !:
class Person
{
int personId;
string firstName;
string lastName;
string alias;
DateTime creationDate;
public int PersonId
{
get { return personId; }
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public string Alias
{
get { return alias; }
set { alias = value; }
}
public DateTime CreationDate
{
get { return creationDate; }
}
//for adding new person object
public Person(string first_name, string last_name, string alias)
{
}
//internal usage
public Person()
{
}
public void Save()
{
//save new person object in DB or update...
}
public static Person GetPerson(int personId)
{
Person p = null;
//call sproc and load from database...
p = new Person();
p.personId = 10;
p.firstName = "First Name";
p.lastName = "Last Name";
p.alias = "Alias";
return p;
}
}
class Comment
{
int commentId;
int personId;
string comment;
DateTime creationDate;
public int CommentId
{
get { return commentId; }
set { commentId = value; }
}
public int PersonId
{
get { return personId; }
set { personId = value; }
}
public string Comment1
{
get { return comment; }
set { comment = value; }
}
public DateTime CreationDate
{
get { return creationDate; }
set { creationDate = value; }
}
public Comment(int person_id, string comment)
{
}
public Comment()
{
}
public void Save()
{
//save or update to DB
}
public static List<Comment> GetComments()
{
List<Comment> comments = null;
//get data from db and load...
comments = new List<Comment>();
comments.Add(new Comment() {
commentId = 1,
personId = 10,
comment = "this is one comment",
CreationDate = DateTime.Now });
comments.Add(new Comment() {
commentId = 1,
personId = 11,
comment = "this is another comment",
CreationDate = DateTime.Now });
return comments;
}
}
Actually why not use entity framework? It treats your database as objects.
Edit:
Basically you can use entity framework to creates database objects for you.
It can maps your table into objects, TComments, TPerson objects etc.
And creates relationship between them with your foregin key.
i.e In your case, because your TPerson table contains Foregin Keys of TComments.
The TPerson object created by Entity Framework will contains a collection of TComment for each TPerson. You will only have to write LINQ to take them out.
And when you modify those object, you only need to execute the Save() method of the entity container and it will save to the database.
This is definitely not a classic case of inheritance - typically, you want to use inheritance to reflect an "is a" relationship - "car is a vehicle", for instance. There's no logical way in which you might say "comment is a person"; you could, of course, say that both comment and person are "things you can store into a database".
Whilst I agree with both King Chan and ken2k that you would probably want to use an existing ORM tool for this, it would help to read up on the underlying concepts of OO design first. I'd recommend Craig Larman's book "Applying UML and patterns" - it's technology-agnostic, but has a great description of how to map objects to a database.
This appears to be a one-to-many association. I wouldn't use inheritance here.
What I would use is an ORM framework. I recommend using Entity Framework.
Step 1: create a database with the schema you provided (actually you could also start from code or model but that's another topic)
Step 2: import your database in Visual Studio and create an entity model from it. This will create classes (called "entities") to represent your objects. This is a mapping between the tables of your database and the objects you manipulate in your application.
Step 3: that's all. Now you can retrieve all your comments from your DB using something like the following lines:
foreach (Comment comment in MyContext.Comments)
{
// Here you have access to the associated Person
Person = comment.Person;
}
Also it'll be easy to get all the comments associated with a person thanks to the one-to-many association:
var comments = Person.Comments;

Complex mapping with tables without IDs

Short version:
Is it possible in NHibernate to create a mapping for a class that has no corresponding table in the database and specify for each property where the data should come from?
Long version:
I have the following class:
public class TaxAuditorSettings
{
private readonly IList<Month> _allowedMonths = new List<Month>();
private readonly IList<Company> _allowedVgs = new List<Company>();
public IList<Month> AllowedMonths
{
get { return _allowedMonths; }
}
public IList<Company> AllowedVgs
{
get { return _allowedVgs; }
}
}
The class Company is a normal entity that is mapped to a table.
The class Month is a simple class without ID or existing mapping (Constructor and error checking removed for brevity):
public class Month
{
public int MonthNumber { get; set; }
public int Year { get; set; }
}
My database has the following two tables:
Table TAX_AUDITOR_ALLOWED_COMPANIES has only one column COMPANY_ID that is a FK to the table COMPANY and has a UNIQUE index.
Table TAX_AUDITOR_ALLOWED_MONTHS has two columns MONTH_NUMBER and YEAR. There is a UNIQUE index spanning both columns.
I would like to map TaxAuditorSettings such that I can ask my NHibernate session for an object of this type and NHibernate then should put the contents of TAX_AUDITOR_ALLOWED_MONTHS into the list TaxAuditorSettings.AllowedMonths and the companies referenced in TAX_AUDITOR_ALLOWED_COMPANIES into the list TaxAuditorSettings.AllowedCompanies.
Is this even possible? If so, how? If not, how would you do it instead?
Please note: I can change the database if necessary.
not quite what you requested for but here goes
public TaxAuditorSettings GetAuditorSettings(ISession session)
{
// assuming there is a ctor taking the enumerables as parameter
return new TaxAuditorSettings(
session.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>(),
session.CreateCriteria<Company>()
.Add(NHibernate.Criterion.Expression.Sql("Id IN (SELECT COMPANY_ID FROM TAX_AUDITOR_ALLOWED_COMPANIES)"))
.Future<Company>())
}
class MonthResultTransformer : IResultTransformer
{
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
return new Month
{
MonthNumber = (int)tuple[0],
Year = (int)tuple[1],
}
}
}
Update: saving
public void SaveOrUpdate(ISession session, TaxAuditorSettings settings)
{
using (var tx = session.BeginTransaction())
{
// get whats in the database first because we dont have change tracking
var enabledIds = session
.CreateSqlQuery("SELECT * FROM TAX_AUDITOR_ALLOWED_COMPANIES")
.Future<int>();
var savedMonths = session
.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>();
foreach (var id in settings.AllowedVgs.Except(enabledIds))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_COMPANIES Values (:Id)")
.SetParameter("id", id).ExecuteUpdate();
}
foreach (var month in settings.AllowedMonths.Except(savedMonths))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_MONTHS Values (:number, :year)")
.SetParameter("number", month.Number)
.SetParameter("year", month.Year)
.ExecuteUpdate();
}
tx.Commit();
}
}
Note: if you can change the database it would be much easier and performant to sanitise the tables
I would do it this way.
public class MonthMap : ClassMap<Month>{
public MonthMap(){
CompositeId()
.KeyProperty(x=>x.MonthNumber,"MONTH_NUMBER")
.KeyProperty(x=>x.Year);
Table("TAX_AUDITOR_ALLOWED_MONTHS");
}
}
Add a column to the COMPANY table called TaxAuditable and map it to a bool property. Update the column to be 1 where a matching row is found in TAX_AUDITOR_ALLOWED_COMPANIES. Then remove the table TAX_AUDITOR_ALLOWED_COMPANIES as it serves no real purpose.
Now you have a Company with an appropriate property on it you can query Company's where TaxAuditable is true and pass them into a method along with the Months to do your work/calculations etc... Something like this perhaps?
var taxAuditableCompanies = session.QueryOver<Company>()
.Where(x=>x.TaxAuditable==true).Future();
var months=session.QueryOver<Month>().Future();
var myService = new MyService();
myService.DoSomeWork(taxAuditableCompanies, months);

Categories

Resources