I have 2 classes which hold data for restaurants.
Status.cs
public class Status
{
[Required]
public int StatusId { get; set; }
[Required]
[DisplayName("Status")]
public string Name { get; set; }
}
Restaurant.cs
public class Restaurant
{
public int RestaurantId { get; set; }
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Telephone { get; set; }
[Required]
public int StatusId { get; set; }
// NAVIGATION PROPERTIES
public virtual Status Status { get; set; }
}
I am trying to seed data into the database.
First, I seed the status table, then I wish to seed the Restaurants table.
var statuses = new List<Status>
{
new Status { Name = "Draft" },
new Status { Name = "Live" },
new Status { Name = "Invisible" },
new Status { Name = "Discontinued" }
};
statuses.ForEach(a => context.Statuses.Add(a));
var restaurants = new List<Restaurant>
{
new Restaurant { Name = "The Restaurant Name", Email = "email#restaurant.com", Telephone = "012345566787", StatusId = 1 }
};
restaurants.ForEach(a=>context.Restaurants.Add(a));
base.seed(context);
This doesn't work, because it doesn't like the way I try to seed StatusId = 1 into Restaurant.
I know that I can create a new Status within the new Restaurant, however, I have already seeded the statuses into the database. How do I go about setting the Restaurant's Status to Draft???
Do I have to do this every time???
new Restaurant { Name = "The Restaurant Name", Email = "email#restaurant.com", Telephone = "012345566787", StatusId = new Status { Name = "Draft"} }
Will this not literally generate a new row in the status table called Status every time I create a new Restaurant with the status "Draft"?
Don't set the StatusId, set the Status:
var restaurants = new List<Restaurant>
{
new Restaurant {
Name = "The Restaurant Name",
Email = "email#restaurant.com",
Telephone = "012345566787",
Status = statuses.Single(s=>s.Name=="Draft") }
};
That way the StatusID is set by the context and uses the same ID as the Status.
If you want to reference the Status in the list by Index (e.g. statuses[0] for "Draft") that works too; selecting by name is more appropriate, though, IMHO.
EDIT
Just read the end of your question - so to answer that:
When you create a "new" Status and attach it to your Restaurant, the context doesn't know that you want to use the existing status of "Draft", so it does what you tell it - creates a new one. When you attach an existing Status from the context is used that ID since it's the same instance.
Related
I have five tables Agent, AgentAddress , Address , ContactInfo and AgentContactInfo.
Here AgentAddress and AgentContactInfo are bridge table.
I have to Import 10000's of row from excel and save.
What is the best approach to save the file
Now I am using following code but getting exception for large data:
var transaction await_context.Database.BeginTransactionAsync();
try
{
foreach (var item in getExcelData)
{
var countryId = await _context.Country.Where(x => x.CountryName == "Nepal").Select(y => y.CountryId).SingleOrDefaultAsync();
Entities.Agent.Agent newAgents = new()
{
CountryId = countryId,
AgentOfficeName = item.Name,
Branch = item.Branch,
ContactPerson = item.ContactPerson,
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
await _context.Agent.AddAsync(newAgents);
await _context.SaveChangesAsync();
var addressTypeListItemId = _context.ListItem.FirstOrDefault(x => x.ListItemSystemName == "Permanent").ListItemId;
Address newAddress = new Address
{
AddressTypeListItemId = addressTypeListItemId,
City = item.City,
State = item.State,
District = item.District,
StreetName = item.Address,
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
await _context.Address.AddAsync(newAddress);
await _context.SaveChangesAsync();
AgentAddress agentAddress = new AgentAddress
{
AgentId = newAgents.AgentId,
AddressId = newAddress.AddressId,
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
await _context.AgentAddress.AddAsync(agentAddress);
await _context.SaveChangesAsync();
var contactTypePhoneId = await _context.ListItem.Where(x => x.ListItemSystemName == "PhoneNumber").Select(y => y.ListItemId).SingleOrDefaultAsync();
ContactInfo phoneNumbers = new ContactInfo
{
ContactNumber = item.Telephone,
ContactTypeListItemId = contactTypePhoneId,
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
await _context.ContactInfo.AddAsync(phoneNumbers);
await _context.SaveChangesAsync();
await _context.AgentContactInfo.AddAsync(new AgentContactInfo
{
AgentId = newAgents.AgentId,
ContactInfoId = phoneNumbers.ContactInfoId,
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
});
await _context.SaveChangesAsync();
}
await transaction.CommitAsync();
}
My Entities:
public class Agent : BaseEntity
{
[Key]
public int AgentId { get; set; }
[Required]
public string AgentOfficeName { get; set; }
public DateTimeOffset? OpenedDate { get; set; }
...
...
[Required]
[ForeignKey(nameof(Country))]
public int CountryId { get; set; }
public Country Country { get; set; }
[ForeignKey(nameof(ListItem))]
public int? AgentTypeListItemId { get; set; }
public ListItem ListItem { get; set; }
}
[Index(nameof(AgentId), nameof(AddressId), IsUnique = true)]
public class AgentAddress : BaseEntity
{
[Key]
public int AgentAddressId { get; set; }
[Required]
[ForeignKey(nameof(Agent))]
public int AgentId { get; set; }
public Agent Agent { get; set; }
[Required]
[ForeignKey(nameof(Address))]
public int AddressId { get; set; }
public Entities.Address.Address Address { get; set; }
}
[Index(nameof(AgentId), nameof(ContactInfoId), IsUnique = true)]
public class AgentContactInfo : BaseEntity
{
[Key]
public int AgentContactInfoId { get; set; }
[Required]
[ForeignKey(nameof(Agent))]
public int AgentId { get; set; }
public Agent Agent { get; set; }
[Required]
[ForeignKey(nameof(ContactInfo))]
public int ContactInfoId { get; set; }
public ContactInfo ContactInfo { get; set; }
}
Short Version
Remove all attempts to save, commit or manually control transactions and just call SaveChangesAsync at the end.
Don't try to retrieve primary keys one by one. EF Core will take care of creating the relations and fixing up foreign keys. Just create valid objects and let EF Core figure out the insertion order.
Don't load lookup data inside the loop either. That just wastes time loading the exact same data multiple times.
In the common Blog and Posts example used by most tutorials, you only need to create a Blog with some Posts, and EF Core will take care of insertions and primary keys:
using(var ctx=new SomeContext())
{
var blog=new Blog { Url = "http://blogs.msdn.com/adonet" }
blog.Posts.Add(new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" });
blog.Posts.Add(new Post { Title = "Second Post", Content = "..." });
blog.Blogs.Add(blog);
await ctx.SaveChangesAsync();
}
Explanation
Normally, an ORM shouldn't be used for ETL jobs like data loading. There are no entities in a data loading job, or rather the entities are Row, Column and Transformation, not Address and Name.
10K rows is very little data though and EF Core can easily handle this if used properly. A DbContext is a Unit-of-Work that tracks all changes. Those changes are committed when SaveChanges is called only once at the end of the work. There's no reason to use an explicit transaction because SaveChanges uses a transaction internally.
Furthermore EF Core already batches all changes but can be configured to use a specific batch size.
This means that all you need to do is remove code. Simply creating the DbContext, adding the classes and calling SaveChangesAsync at the end is enough to insert data in batches, in a single transaction:
using(var context = new MyContext(...))
{
var countryId = await context.Country
.Where(x => x.CountryName == "Nepal")
.Select(y => y.CountryId)
.SingleOrDefaultAsync();
foreach (var item in getExcelData)
{
var newAgents = new Agent()
{
AgentOfficeName = item.Name,
...
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
var newAddress = new Address
{
AddressTypeListItemId = addressTypeListItemId,
City = item.City,
...
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
var agentAddress = new AgentAddress
{
Address = newAddress
InsertPersonId = personId,
InsertDate = DateTimeOffset.UtcNow
};
agent.Addresses.Add(agentAddress);
context.Agents.Add(newAgent);
}
await context.SaveChangesAsync();
}
The call context.Agents.Add(newAgent); will add newAgent and all related objects to the DbContext in the Added state. When SaveChanges is called, EF Core will insert them in the proper order. First all child entities in batches, retrieving their PKs. Then it will fix up the parent entities and insert all of them in batches.
EF Core won't just INSERT one row after the other either, it will use an INSERT ... OUTPUT inserted.ID with multiple value rows to insert multiple objects and return their IDs in the same query.
I'm using vs2017 with entityframework 6.1.0 and winforms . As for entityframework i use code-first. I need to make a movie app, I have made all classes for them Movie, Genre and Cast(actors).
All Genres are pre inserted in the database. When using update-database everything is created including joining tables moviegenre and movie cast and also foreignkeys. When i insert a movie object. it links the id's from genre cast and movies but it also reinserts every genre which means i have duplicates. I only want the linking of course. So far this is my code.
movie class:
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MovieId { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
//[ForeignKey("GenreId")]
public virtual List<Genre> Genre { get; set; }
//[ForeignKey("CastId")]
public virtual List<Cast> cast { get; set; }
[Required]
public int GenreId { get; set; }
[Required]
public int CastId { get; set; }
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; set; }
public Movie()
{
Genre = new List<Genre>();
cast = new List<Cast>();
}
}
Genre and cast (the same for both classes)
public class Genre
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GenreId { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public List<Movie> Movies { get; set; }
}
and of course my code (this piece of code is from the button click event to add a movie into the db.):
private void btnAddMovie_Click(object sender, EventArgs e)
{
List<Genre> genrelist = new List<Genre>();
List<Cast> castlist = new List<Cast>();
var movie = new Movie();
movie.Name = txtTitle.Text;
movie.ReleaseDate = released;
//creating lists
foreach (string item in lbgenre2.Items)
{
var genre = new Genre();
genre.Name = item;
genrelist.Add(genre);
}
foreach (string item in lbCast2.Items)
{
var cast = new Cast();
cast.Name = item;
castlist.Add(cast);
}
movie.Genre = genrelist;
movie.cast = castlist;
_context.movies.Add(movie);
Save();
}
private async void Save()
{
await _context.SaveChangesAsync();
}
What am I doing wrong that it links and reinserts it?
Your problem is because you are creating the Generes again, and again every time you add a new movie and you have a Identity Key, so, no exception will throw.
foreach (string item in lbgenre2.Items)
{
//Here is your problem, you are creating a new Genere instead of using the already created
var genre = new Genre();
genre.Name = item;
genrelist.Add(genre);
}
So, instead of creating, use ef to get the existing ones
foreach (string item in lbgenre2.Items)
{
//try to get the genere from database
var genre = _context.generes.FirstOrDefault(x => x.Name == item);
//if it doesn't exist..
if(genre == null)
{
//Create it
genre = new Genre();
//And set the name
genre.Name = item;
}
//but, if it already exist, you dont create a new one, just use the existing one
genrelist.Add(genre);
}
Entity Framework cannot figure out if a Genre or Cast already exists, even if you make an instance of one of them, with identical properties to one which exists in the database. Instead you need to get the existing genres and cast from the database and apply them. And only create a new instance, if it is a completely new genre or cast, which is not in the database:
Pseudo-code:
SaveMovie(Movie movie)
{
var existingGenres = GetGenresFromDatabase();
var movieGenres = GetGenresFromListBox();
foreach (var genre in movieGenres)
{
if (genre in existingGenres)
{
existingGenre = existingGenres.Where(genreId = genre.Id);
movie.Genres.Add(existingGenre)
}
else
{
movies.Add(genre)
}
}
dbcontext.Add(movie);
dbcontext.SaveChanges();
}
Im trying to use Dapper in an ASP.Net Core application to map multiple tables to one object with other objects as its properties.
My tables are as follows (just basic summary):
user table
address table (no user ids stored in this table, this table is just address records)
address_type table (lookup table)
phone_number table (no user ids stored in this table, this table is
just phone records)
phone_number _type table (lookup table)
user_has_address table - this table has a user_id, address_id and address_type_id
user_has_phone_number table - this table has a
user_id, phone_number _id and phone_number _type_id
Basically whats happening is results get returned fine if all the users have no address or phone records or if only the first user in the list has address/phone records. What I want is if a user has an address/phone number then that dictionary is populated, otherwise I still want all the user info but that address/phone number dictionary will be empty.
My objects look like the following:
public class User
{
public uint id { get; set; }
public DateTime modified_date { get; set; }
public uint modified_by { get; set; }
public string user_name { get; set; }
public uint company_code { get; set; }
public string email { get; set; }
public bool active { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public Dictionary<uint, Address.Address> addresses { get; set; }
public Dictionary<uint, Address.PhoneNumber> phone_numbers { get; set; }
}
public class Address
{
public uint address_id { get; set; }
public AddressType address_type { get; set; }
public string address_line1 { get; set; }
public string address_line2 { get; set; }
public string address_line3 { get; set; }
public string city { get; set; }
public string state { get; set; }
public string country_code { get; set; }
public string postal_code { get; set; }
public sbyte is_po_box { get; set; }
}
public class AddressType
{
public uint id { get; set; }
public string name { get; set; }
}
public class PhoneNumber
{
public uint id { get; set; }
public PhoneNumberType phone_number_type { get; set; }
public string phone_number { get; set; }
public string phone_ext { get; set; }
}
public class PhoneNumberType
{
public uint id { get; set; }
public string name { get; set; }
}
Here is my function where I try to use Dapper to map to the User class:
public List<User> GetUsersByStatus(uint companyCode, string status)
{
if (companyCode == 0)
throw new ArgumentOutOfRangeException("companyID", "The Company ID cannot be 0.");
List<User> Users = new List<User>();
try
{
string sql = #"SELECT u.*, ad.*, adt.*, p.*, pt.*
FROM master.user u
LEFT JOIN master.user_has_address AS uha ON uha.user_id = u.id
LEFT JOIN master.address AS ad ON ad.id = uha.address_id
LEFT JOIN master.lookup_address_type adt ON adt.id = uha.address_type_id
LEFT JOIN master.user_has_phone_number AS uhp ON uhp.user_id = u.id
LEFT JOIN master.phone_number AS p ON p.id = uhp.phone_number_id
LEFT JOIN master.lookup_phone_number_type pt ON pt.id = uhp.phone_number_type_id
WHERE u.company_code = " + companyCode;
switch (status)
{
case "1":
// Active Status.
sql = sql + " AND (u.active = TRUE)";
break;
case "2":
// Retired Status.
sql = sql + " AND (u.active = FALSE)";
break;
}
sql = sql + " ORDER BY u.user_name";
using (var conn = new MySqlConnection(connectionString))
{
conn.Open();
var userDictionary = new Dictionary<uint, User>();
conn.Query<User, Address, AddressType, PhoneNumber, PhoneNumberType, User>(sql, (u, ad, adt, p, pt) =>
{
User user;
if (!userDictionary.TryGetValue(u.id, out user))
userDictionary.Add(u.id, user = u);
if (ad != null && adt != null)
{
Address address = ad;
address.address_type = new AddressType() { id = adt.id, name = adt.name };
if (user.addresses == null)
user.addresses = new Dictionary<uint, Address>();
if (!user.addresses.ContainsKey(adt.id))
user.addresses.Add(adt.id, address);
}
if (p != null && pt != null)
{
PhoneNumber phone = p;
phone.phone_number_type = new PhoneNumberType() { id = pt.id, name = pt.name };
if (user.phone_numbers == null)
user.phone_numbers = new Dictionary<uint, PhoneNumber>();
if (!user.phone_numbers.ContainsKey(pt.id))
user.phone_numbers.Add(pt.id, phone);
}
return user;
},
splitOn: "id,id,id,id").AsQueryable();
Users = userDictionary.Values.ToList();
}
}
catch(Exception ex)
{
//TO DO: log exception
}
return Users;
}
I've tried tracing through it and in cases where the 3rd user (for example) has address/phone records, it seems to grab the first 2 users fine but then jump right out of the query portion when it gets to the record with address/phone numbers and then it returns an empty Users list.
Does anyone have any idea what Im doing wrong?
I'm not sure this is the solution, but I have a problem with this code:
User user;
if (!userDictionary.TryGetValue(u.id, out user))
userDictionary.Add(u.id, user = u);
Remember that u is parameter of the lambda, and will be changed during execution. I would do this:
User user;
if (!userDictionary.TryGetValue(u.id, out user))
{
user = new User(u); // Make a new instance of user
userDictionary.Add(u.id, user);
}
Also you should definitely use parameters for your query:
WHERE u.company_code = #CompanyCode";
And finally I don't think it should be the responsibility of this code to construct dictionaries for holding addresses and phone numbers. The User constructor should take care of that.
Unfortunately, I must have 50 rep to add a comment, so I'll make this an answer.
Are all the fields in the DB concerning phone and address non-nullable? If no, then the mistake could be that it can't match them with your C# classes. In this case, declare immutable types nullable so that they match DB types. Like this:
int? val;
Turns out the main issue was the fact that I left the address_id parameter in the Address class named the way it was, it should ahve been just 'id'. I updated that and it works now, I did also update my code according to Palle Due's suggestion so that may have contributed as well.
I'm trying to update a foreign key in EF6 (Code First) in an ASP.Net MVC manner.
Let me explain :
My entities
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
My database
Table Countries with 2 records :
Id = 1, Name = France
Id = 2, Name = Canada
Table People with 1 record :
Id = 1, Name = Nicolas, Country_Id = 1
My code
// In a MVC application, these class has been retrieved via EF in a previous page. So now, we've lost all "proxy" informations
var p = new Person() { Id = 1, Name = "Nicolas" };
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
// Persist all in DB
using (var db = new TestDbContext())
{
db.Persons.Attach(p); // Reattach to EF context
db.Entry<Person>(p).State = EntityState.Modified; // Flag modified state
db.SaveChanges(); // Generate only modification on field "name"
}
My issue
When the previous code is executed, the generated SQL never include the country_Id field from the person table.
My "not" issue
I know that it works perfectly when doing all these lines of codes in one EF context but in my case, I will have the data coming from my ASP.Net MVC page.
I would also like to avoid to retrieve the existing data and modify each field one by one
By retrying #ivan solution, I was first able to do what was wanted.
Here are the modifications done :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
public int Country_Id { get; set; }
}
// ...
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
p.Country_Id = 2;
But now, I get an exception when getting entities from the database.
Using this code :
// Retrieve data first
using (var db = new TestDbContext())
{
var p2 = db.Persons.First();
}
I get the following SqlException : "Invalid column name 'Country_Id1'."
Does anyone have any clues to be able to retrieve data and to update the foreign key ?
Not quiet sure how to word the title for this one so feel free to edit if it isn't accurate.
Using an example, what I am trying to accomplish is update a record in table foo, and then create new records in a subsequent table that has the foo tables PK as foreign key, think One-to-Many relationship.
How do I update a table that has foreign key constraints and create a new related record(s) in these subsequent table(s)?
Currently I am using Entity Framework 6 to .Add and .Attach entities to the context and save them to the database.
Edit
To clarify further what I am trying to achieve, the below object is a cut down made up example what I am trying to save to the context. If I try to .Add intObj after "Billy Bob" has already been created because he has bought a new car, another service, or his tyres have changed it will create a new Billy Bob record (duplicate) and the corresponding related tables.
intObj.FirstName = "Billy";
intObj.Lastname = "Bob";
intObj.Important = 100;
intObj.LastSeen = DateTime.Now.Date;
intObj.Cars = new List<Car>{
new Car{
Model = "Commodore",
Make = "Holden",
YearMade = DateTime.Today.Date,
Odometer = 15000,
EmailWordCount = 500,
TyreStatuss = new List<TyreStatus>{
new TyreStatus{
Tyre1 = "Good",
Tyre2 = "Good",
Tyre3 = "Okay",
Tyre4 = "Okay"
}
},
Services = new List<Service>{
new Service{
Cost = "$500",
Time = "2 Days",
Date = DateTime.Today
}
},
}
};
Thanks
In the following snippets you have Employee class, which references two more entities: a collection of Assignment and a single Country.
ORMs like EF , NHibernate, etc... have a feature known as Transitive Persistence, that is, if an object (Assignment and Country) is referenced by a persistent one (Employee), then Assignments and Country will eventually become persistent too when, in your EF case, SaveChanges method gets invoked in the Context, without you explicitly save them.
public class Employee
{
public virtual Guid Id { get; protected set; }
public virtual string EmployeeNumber { get; set; }
public virtual Country BirthCountry { get; set; }
private ICollection<Assignment> _assignment = new List<Assignment>();
public virtual ICollection<Assignment> Assignments
{
get
{
return _assignment;
}
set
{
_assignment= value;
}
}
}
public class Assignment
{
public virtual Guid Id { get; protected set; }
public virtual DateTime BeginTime { get; set; }
public virtual DateTime EndTime { get; set; }
public virtual string Description{ get; set; }
}
public class Country
{
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
}
//Somewhere in your program
private void SaveAllChanges()
{
_db = new EFContext();
//Creating a new employee here, but it can be one loaded from db
var emp = new Employee { FirstName = "Emp Name",
LastName = "Emp Last", EmployeeNumber = "XO1500"
};
emp.BirthCountry = new Country { Name = "Country1" };
emp.Assignment.Add(new Assignment{ BeginTime = DateTime.Now,EndTime=DateTime.Now.AddHours(1) });
//Only employee is explicitly added to the context
_db.Employees.Add(emp);
//All the objects in the employee graph will be saved (inserted in this case) in the db.
_db.SaveChanges();
}
}
EDIT:
That is very similar to my code above, once "Billy Bob" is created you only need to update it, and that include any new service he buy;
Pseudo code:
var bob = _db.Clients.SingleOrDefault(c=> c.Id = "Bob Row Id")
//Bob buy a car:
bob.Cars.Add(new Car()...)
//...and change tire 1 from an old car
var car = bob.Cars.SingleOrDefault(c=> c.Id = "Car Row Id")
car.TireStatus.Tire1 = "New"
....
//Persist all changes
//Existing objects will be updated..., and the new ones created in this process will be inserted
_db.SaveChanges()
Let me know if this clarify your ideas