I have a table 'Comments' that I want to store lists of comments with dates on. These comments can belong to three different classes each one with its own table on the db.
My first attempt was doing this:
public class Comment
{
public int CommentID { get; set; }
public virtual int OwnerID { get; set; }
public string Description { get; set; }
}
But I cant figure out how to use it with three different classes.
My next option was:
public class Comment
{
public int CommentID { get; set; }
public virtual IAcceptComment Owner { get; set; }
public string Description { get; set; }
}
And I assign the interface to three classes but I get an error trying to configure the context for one of the entities: (An explicit conversion exists (are you missing a cast?))
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>().HasMany<Comment>(e => e.Comments).WithMany(c => c.Owner);
}
I have used Model First to see how EF generates the classes and this is what it does:
public class Comments
{
public int Id { get; set; }
public string Comment { get; set; }
public virtual ICollection<Consultant> Consultant { get; set; }
public virtual ICollection<Event> Event { get; set; }
public virtual ICollection<Company> Company { get; set; }
}
Isn't there any way to avoid the creation of Navigation Properties? Can you think of any other approximation to the problem?
Thanks.
I think you can use Inheritance in EF
Inheritance Refernece
TPT Inheritance Usage msdn
TpT Usages
This will help you to avoid navigation properities
make Query like this
'var db = new MySchoolEntities();
foreach (var department in db.Departments)
{
Console.WriteLine();
Console.WriteLine("The {0} department has the following courses:",
department.Name);
Console.WriteLine();
Console.WriteLine(" All courses");
foreach (var course in department.Courses)
{
Console.WriteLine(" The Course Name {0}", course.Title);
}
Console.WriteLine();
if (department.Courses.OfType<OnlineCourse>().Count() > 0)
{
Console.WriteLine(" Online courses are");
foreach (var online in department.Courses.OfType<OnlineCourse>())
{
Console.WriteLine("Online Course is {0} & Link : {1} ", online.Title, online.URL);
}
}
Console.WriteLine();
Console.WriteLine(" Onsite courses are");
foreach (var onsite in department.Courses.OfType<OnsiteCourse>())
{
Console.WriteLine(" Online Course Venue : {0} ", onsite.Location);
}
}
Console.ReadLine();
Hope this will make you avoid lengthy Navigations.
Related
I have these models and context in my application :
public class Department
{
public int Id { get; set; }
public string Name { get; set;}
public virtual ICollection<Student> Students { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Department Department { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
}
Below is my code in Program.cs class :
class Program
{
static void Main(string[] args)
{
using (var context = new TestContext())
{
var students = context.Students.SqlQuery("Select * from dbo.Students").ToList();
context.Departments.Load();
Console.WriteLine(students[0].Department.Name);
Console.ReadLine();
}
}
}
Although related object - Department is loaded in the context by the line - context.Departments.Load(), still when the department name is printed in console entity framework fires a query in the database to fetch the related object. Shouldnt this query for related object fetching not be fired since the objects are already loaded in the context. ?
If i change the code to below -
var students = context.Students.ToList();
context.Departments.Load();
Console.WriteLine(students[0].Department.Name);
Then when u access student[0].Department.Name , Ef doestnot fire a sql query to load department property.
Apparently Change Tracker relationship fix-up doesn't work with the combination of Independent Associations and raw SQL queries.
To fix just add Foreign Key property to Student. eg
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int DepartmentId { get; set; }
public virtual Department Department { get; set; }
}
I have been working on a shop site project, using asp.net core spa templates provided with the latest VS2017, and have come across an issue that I haven't had before, possibly because until now my apps were quite simple!
I know what the problem is and where, I just can't fix it. I have a product model which has a collection of "Attributes" and a collection of "Variations" (different colour size, etc) and those variations also have attributes, so if the same Attribute shows up in the Variation (VAttributes), as is already in the main "Attributes" I get the error
InvalidOperationException: The instance of entity type
'ProductAttribute' cannot be tracked because another instance with the
key value 'Id:2' is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached.
The best answer I found was here : https://stackoverflow.com/a/19695833/6749293
Unfortunately, even with the above check I got the error, I even tried making a list of attached attributes, and if the vattribute matched one of the items in the list, I didn't attach it. In fact I found that even if I don't attach (_context.attach()) any of the vAttributes, it still throws the error!.
Here's the code in question:
public async Task<Product> Create(Product product)
{
try
{
foreach (var variation in product.Variations)
{
foreach (var vAttr in variation.VAttributes)
{
bool isDetached = _context.Entry(vAttr).State == EntityState.Detached;
if (isDetached)
_context.Attach(vAttr);
}
}
foreach (var attribute in product.Attributes)
{
bool isDetached = _context.Entry(attribute).State == EntityState.Detached;
if (isDetached)
_context.Attach(attribute);
}
foreach (var category in product.Categories)
{
_context.Attach(category);
_context.Attach(category).Collection(x => x.Children);
}
_context.Products.Add(product);
await Save();
return product;
}
catch (Exception)
{
throw;
}
}
The models for the 3 objects are as follows:
public class Product
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string StockRef { get; set; }
public DateTime? LastModified { get; set; }
//image needed
public ICollection<ProductCategory> Categories { get; set; }
public ICollection<ProductAttribute> Attributes { get; set; }
public ICollection<ProductVariation> Variations { get; set; }
public Product()
{
Attributes = new List<ProductAttribute>();
Variations = new List<ProductVariation>();
Categories = new List<ProductCategory>();
}
}
Variation:
public class ProductVariation
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<ProductAttribute> VAttributes { get; set; }
//needs images
public decimal VPrice { get; set; }
public string VStockRef { get; set; }
}
Finally the Attribute:
public class ProductAttribute
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AttributeCategory")]
public int AttributeCategoryId { get; set; }
public virtual AttributeCategory AttributeCategory { get; set; }
}
Most help I found when searching was more related to having repo's injected as singletons, or HttpPut methods where the code had check for existence omitting the .AsNoTracking() or it was a mistake that they had the second instance in some way, where I am aware of the second instance, I just don't know how to prevent it from being tracked!
EDIT: I found that adding a foreign key on the ProductVariation model to the Product that was being created failed as it was only a temp key!? anyway removed it from the variation model, so have updated my code. Also thought I'd add one of my earler failed attempts, that led to all of the foreach loops.
_context.AttachRange(product.Attributes);
_context.AttachRange(product.Categories);
_context.AttachRange(product.Variations);
_context.Add(product);
I believe you can allow EF to handle the tracking.
public virtual bool Create(T item)
{
try
{
_context.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
This allows for you to save the entire object structure without worring about attaching items.
var newProduct = new Product();
newProduct.Categories.Add(cat);
newProduct.Attributes.Add(att);
newProduct.Variations.Add(vari);
Create(newProduct);
I created a MSSQL database with tables containing foreign keys. I then created a new MVC 4 web application. I used Entity Framework to generate my controllers/models/views. In the models, tables that were linked using foreign keys appear as ICollections. For example:
public partial class Test
{
public Test()
{
this.Questions = new HashSet<Question>();
this.Users = new HashSet<User>();
}
public int testId { get; set; }
public string name { get; set; }
public string description { get; set; }
public Nullable<int> pointValue { get; set; }
public Nullable<int> numberOfQuestions { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
My question is how am I able to access the data stored in those ICollections in a view? Test.Questions[x] <-- gives errors.
ICollection<T> is just like an IList<T> or T[], so you're going to have to get an element within the collection first then reference its properties. e.g.
Test test = testService.Get(1);
Question question = test.Questions.FirstOrDefault(); // using System.Linq;
if (question.quertionType == ....)
{
}
This was more simple than I expected. I just needed to use a foreach loop:
#foreach (var question in item.Questions)
{
<td>#question.question1</td>
}
Confusing Situation
I have a situation where I have 2 entities where 1 inherits from the other, that need to map to 2 separate tables, but code use should be around the base of the 2 entities.
Details
public class Team
{
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
We have an existing database schema where Employee and EmployeeInfo are separate tables with a FK between EmployeeInfo_Id and Employee_Id.
In our system "managers" will be adding Employee's to the system, with a set of private information (more properties than listed above) like pay, and add them to a Team. Other areas of the system will be using the Team or Employee objects for various other things. We would like to have to code super simple if the mapping can be done.
When a manager creates a new employee we would like the code to look something like this:
public void Foo(string name, decimal pay)
{
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Pay = pay;
// add him/her to the team
_team.Employees.Add(employee); // the idea being that consumers of the Team entity would not get the separate employee info properties
// save the context
_context.SaveChanges();
}
The end result would be that the EmployeeInfo properties entered into the EmployeeInfo table and the base Employee data is entered into the Employee table and added to the Team via the association table TeamEmployees.
So far I'm trying the current mappings, and I get an invalid column named "Discriminator." When just adding an employee to a team.
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
ToTable("Team");
HasKey(t => t.Id);
HasMany(t => t.Members).WithMany(m => m.Teams)
.Map(m =>
{
m.MapLeftKey("Team_Id");
m.MapRightKey("Employee_Id");
m.ToTable("TeamEmployees");
});
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
ToTable("Employee");
ToTable("EmployeeInfo");
HasKey(t => t.Id);
Property(p => p.Name);
HasMany(m => m.Teams)
.WithMany(t => t.Members)
.Map(m =>
{
m.MapLeftKey("Employee_Id");
m.MapRightKey("Team_Id");
m.ToTable("TeamEmployees");
});
}
}
Also, if I take the many-to-many between team and employee out of the mix I get a FK exception on Employee_Id to EmployeeInfo_Id.
Thanks, JR.
Discriminator is a column that's being added to your table when you use Table Per Hierarchy approach.
I think what you're looking for is "Table per Type (TPT)". Decorate your EmployeeInfo class as follows:
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
Or add below to your OnModelCreating event:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<EmployeeInfo>().ToTable("EmployeeInfo");
...
}
Or, create the following class and use it like modelBuilder.Configurations.Add(new EmployeeInfoConfiguration()); in OnModelCreating method:
public class EmployeeInfoConfiguration : EntityTypeConfiguration<EmployeeInfo>
{
public EmployeeInfoConfiguration()
{
ToTable("EmployeeInfo");
}
}
This will cause EF to create EmployeeInfo table with necessary constraints.
Also, it's good to initialize your collections in your objects' constructors to prevent null exception. For example in Team class:
public Team()
{
this.Employees = new HashSet<Employee>();
}
I copied your code exactly, and changed the following parts:
public class Team
{
public Team()
{
this.Members = new HashSet<Employee>();
}
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public Employee()
{
this.Teams = new HashSet<Team>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
In the DbContext, no changes:
public partial class TestEntities : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<EmployeeInfo> Employee_Info { get; set; }
public DbSet<Team> Teams { get; set; }
}
and your working Foo method:
public static void Foo(string name, decimal pay)
{
var _team = new Team();
var context = new TestEntities();
context.Teams.Add(_team);
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Amount = pay;
context.Employees.Add(employee);
context.SaveChanges();
// add him/her to the team
_team.Members.Add(employee);
// save the context
context.SaveChanges();
}
Finally, remove ToTable("EmployeeInfo"); part from EmployeeConfiguration since you have mentioned this correctly in your mode creating event.
For more info about Table Per Type approach, check out this great article.
I really need someone to help me to fully understand how to do many-to-many relationship with Entity Framework 4 CTP 5, POCO. I need to understand 3 concepts:
How to config my model to indicates
some tables are many-to-many.
How to properly do insert.
How to properly do update.
Here are my current models:
public class MusicSheet
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Key { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Author
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
As you can see, the MusicSheet can have many Authors or Tags, and an Author or Tag can have multiple MusicSheets.
Again, my questions are:
What to do on the
EntityTypeConfiguration to set the
relationship between them as well as
mapping to an table/object that
associates with the many-to-many
relationship.
How to insert a new music sheets
(where it might have multiple
authors or multiple tags).
How to update a music sheet. For
example, I might set TagA,
TagB to MusicSheet1, but later I need to change the tags to TagA
and TagC. It seems like I need
to first check to see if the tags
already exists, if not, insert the
new tag and then associate it with
the music sheet (so that I doesn't
re-insert TagA?). Or this is
something already handled by the
framework?
Thank you very much. I really hope to fully understand it rather than just doing it without fully understand what's going on. Especially on #3.
In the EF4 CTP5 the relationship is done by default convention when you put public virtual ICollection in each of the classes of the many to many relationship, as you already have done, your context class should look like this:
public class YourContextName : DbContext
{
public DbSet<MusicSheet> MusicSheets { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Author> Authors { get; set; }
}
Very simple you just create a instance of the MusicSheet class and then add all the instances of you authors and tags to each of the collections of Authors and Tags in your MusicSheet, and then add your instance of MusicSheet to your context collection of MusicSheets and then call SaveChanges:
MusicSheet musicSheet = new MusicSheet
{
Title = "Music Sheet 1",
Key = "Key",
Authors = new List<Author>
{
new Author
{
Name = "Author 1",
Bio = "Author 1 biographic text..."
},
new Author
{
Name = "Author 2",
Bio = "Author 2 biographic text..."
}
},
Tags = new List<Tag>
{
new Tag {TagName = "TagA"},
new Tag {TagName = "TagC"}
}
};
var context = new YourContextName();
context.MusicSheets.Add(musicSheet);
context.SaveChanges();
To update you have to load your MusicSheet and remove the tags you don't want and then add the ones you need to add, this is how:
var context = new YourContextName();
var myMusicSheet = context.MusicSheets.First();
//The Tag you wnat to remove.
var tagToRemove = myMusicSheet.Tags.First();
var tagToAdd = new Tag {TagName = "TagX"};
myMusicSheet.Tags.Remove(tagToRemove);
myMusicSheet.Tags.Add(tagToAdd);
context.Entry(myMusicSheet).State = EntityState.Modified;
context.SaveChanges();
You can also find any author and/or tag that you know that exist and added to your MusicSheet and vice versa, but this is the foundation.
Remember this is for the EF4 CTP5 Code first...
Excuse me my English is not my main language, I hope this can help you, best regards from Dominican Republic.
PS: Don't forget to add references to EntityFramework and System.Data.Entity, is your responsibility to do anything else like unit test, validation, exception handling...etc
EDIT:
First you need to add a constructor to your models:
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public Tag()
{
MusicSheets = new List<MusicSheet>();
}
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
...Then you can do something like this:
var context = new YourContextName();
var newMusicSheet = new MusicSheet();
newMusicSheet.Title = "Newly added Music Sheet";
//Your existing Tag.
var existingTag = contex.Tags.Find(3);
existingTag.MusicSheets.Add(existingTag);
context.Entry(existingTag).State = EntityState.Modified;
context.SaveChanges();
You can do the same for all your models.
I hope this can help you!
You do not really need an EntityTypeConfiguration to set the relationship between them. It should work as it is right now. With CTP5 all you have to do to establish a many-to-many relationship is to include ICollection in both entities.
Now about how to perform inserts and deletes, there are two ways I know of. The one I usually use is create an entity for the resultant table of the many-to-many relationship, then create an instance of this entity and feed it with all the data that is required, including instances of the other entities (the ones that have the many-to-many relationship). And finally I simply add it to the repository and commit the transaction (usually using a UnitOfWork class).
Quick example:
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class User
{
public int ID { get; set; }
public string Username{ get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class Bid
{
public int ID { get; set; }
public float Amount { get; set; }
public DateTime Date { get; set; }
public virtual Item Item { get; set; }
public virtual User User { get; set; }
}
Then I would simply create instances of the Bid entity.
public void PlaceBid(User user, Item item, int amount)
{
if (ValidateBid(amount, user, item))
{
Bid bid = new Bid
{
Amount = amount,
Date = DateTime.Now,
User = user,
Item = item
};
try
{
repository.Add(bid);
unitOfWork.Commit();
}
catch (Exception ex)
{
//TODO: Log the exception
throw;
}
}
}