Control a reference in entity framework? - entity-framework-core

enter image description hereControl a reference in entity framework
Open question .Net Core 5 - Entities.
What do you think is the most efficient way to work in MVC mode, having 1 single entity that references itself. example
public class ClassGroupAndSubgroup
{
public int ID { get; set; }
public int SubGroupId { get; set; }
public string Name{ get; set; }
}
ClassGroupAndSubgroup principalGroup { SubGroupId = 0, Name ="Principal"}; ----> Id = 1 (AutoNum)
ClassGroupAndSubgroup SubGroup { SubGroupId = 1, Name ="Item_1_Subgroup1"}; ----> Id = 2 (AutoNum)
ClassGroupAndSubgroup SubGroup_Two { SubGroupId = 1, Name ="Item_2_Subgroup1"}; ----> Id = 3 (AutoNum)
ClassGroupAndSubgroup principalGroup { SubGroupId = 3, Name ="Item_1_Subgroup2"};----> Id = 4 (AutoNum)
What I want is to make reference on itself, but from EntityFramework Core. I did it a long time ago with algorithms in C# and SQL but I can't do it in entity

You want the Foreign Key property to refer to the parent, not the child, eg
public class Foo
{
public int ID { get; set; }
public int ParentFooId{ get; set; }
public Foo Parent {get;set;}
public string Name{ get; set; }
}

Related

asp.net web api server data not syncing with database between BL

Hello I am new to servers and REST API and am trying to extract data from a dynamically created table and the data does not sync with the data in the database.
I have an sql database from which I extracted an entity database in asp.net web project.
This is an example for GET of one entity class (exists in database):
public class EmployeeBL
{
private FSProject1Entities db = new FSProject1Entities();
public List<Employee> GetEmployees(string fname, string lname, string depID)
{
return GetEmployeeSearchResult(fname, lname, depID);
}
}
And this is an example for a method from a class such as I created in order to combine data from 2 tables:
public class ShiftEmployeeDataBL
{
private FSProject1Entities db = new FSProject1Entities();
private List<ShiftEmployeeDataBL> GetEmployeeByShiftID(int id)
{
List<ShiftEmployeeDataBL> shiftEmpData = new List<ShiftEmployeeDataBL>();
foreach (Employee emp in db.Employee)
{//build list... }
return shiftEmpData;
}
My problem is that db.Employee via this GET request path (ShiftEmployeeData) is old data and via Employee GET request is good data (assuming the data was updated via Employee path).
And vice versa - it would appear that if I update Employee via ShiftEmployeeData class, it would appear as good data for ShiftEmployeeData class and not update for Employee.
I have APIcontrollers for both classes.
what is happening? I feel like I am missing something.
I tried closing cache options in browser.
update with code for elaboration:
entity Employee:
public partial class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int StartWorkYear { get; set; }
public int DepartmentID { get; set; }
}
employee update(auto generated by entity model code generation from db):
public void UpdateEmployee(int id, Employee employee)
{
Employee emp= db.Employee.Where(x => x.ID == id).First();
emp.FirstName = employee.FirstName;
emp.LastName = employee.LastName;
emp.StartWorkYear = employee.StartWorkYear;
emp.DepartmentID = employee.DepartmentID;
db.SaveChanges();
}
employeeshiftdata class (not a db table but still in the models folder):
public class EmployeeShiftData
{
public int ID { get; set; } //EmployeeID
public string FirstName { get; set; }
public string LastName { get; set; }
public int StartWorkYear { get; set; }
public string DepartmentName { get; set; }
public List<Shift> Shifts { get; set; }
}
employeeshift GET part of the controller:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class EmployeeShiftDataController : ApiController
{
private static EmployeeShiftDataBL empShiftDataBL = new EmployeeShiftDataBL();
// GET: api/EmployeeShiftData
public IEnumerable<EmployeeShiftData> Get(string FirstName = "", string LastName = "", string Department = "")
{
return empShiftDataBL.GetAllEmployeeShiftData(FirstName, LastName, Department);
}
//...
}
Would need to see the code that interacts with the database, especially the code that makes the updates.
If the changes are written with Entity Framework, are the models themselves properly related with navigational properties?
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeShift> EmployeeShifts { get; set; }
// etc.
}
public class EmployeeShift
{
public int Id { get; set; }
public Int EmployeeID { get; set; }
public Employee Employee { get; set; }
// etc.
}
If those are good, and both models are covered by Entity Framework's context tracking, then both should be updated.

Entity framework 6 use parentid in where clause with lambda expression

I'm new to EF and want to get an entry from my database (SQLite) in the following way:
Classes:
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
public Customer Customer { get; set; }
}
DBContext:
public DbSet<Customer> Customers { get; set; }
public DbSet<Month> Months { get; set; }
Usage:
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.Customer.Id == customer.Id).ToList();
}
shouldContainOneEntry is emtpy, but a test with a delegate and a static variable instead of the lambda expression worked:
private static Guid staticGuid;
public static bool DelegateTest(Month x)
{
return staticGuid == x.Customer.Id;
}
...
staticGuid = customer.Id;
var workingVersion = context.Months.Where(DelegateTest).ToList();
The generated SQL looks correct:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CacheId] AS [CacheId],
[Extent1].[Data] AS [Data],
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [Months] AS [Extent1]
WHERE [Extent1].[Customer_Id] = #p__linq__0
-- p__linq__0: '5cfde6e0-5b3f-437b-84c8-2845b077462d' (Type = AnsiStringFixedLength, IsNullable = false)
Why is the version with the lambda expression not working?
The solution was found by following the hints of IvanStoev.
SQLite stores the Guid by default in a binary format.
Therefor a query like
SELECT * FROM Months WHERE CustomerId = '5cfde6e0-5b3f-437b-84c8-2845b077462d'
delivers an empty result.
Using SQLite Adminstrator the Guid is shown with the binary format.
Using the server explorer in VS the Guid is shown with a string format which lead me to the wrong conclusion that this should work.
After setting the option BinaryGUID of the connection string to false the code works fine.
Based on your declarations
public class Customer
{
public Guid Id { get; set; }
public List<Month> Months { get; } = new List<Month>();
}
public class Month
{
public int Id { get; set; }
public Guid CustomerId{get;set;} // you need to add the foreign Key of the customer ( i will suppose that your foreign key in db called CustomerId
public string CacheId { get; set; }
public string Data { get; set; }
[Required]
[ForeignKey("CustomerId")] // and you need to tell EF which column is the foreign key
public Customer Customer { get; set; }
}
how to use it now
using (var context = new CustomerContext())
{
var customer = context.Customers.First();
context.Database.Log = Console.WriteLine;
var shouldContainOneEntry = context.Months.Where(x => x.CustomerId == customer.Id).ToList();
}
hope it will help you

ef code first increase id by 1

Im using EF to update my database.
This is my model:
//Resource
public long ResourceId { get; set; }
public string Name { get; set; }
public string Descritption { get; set; }
public string UserId { get; set; }
public int ResourceTypeId { get; set; }
public byte[] Data { get; set; }
public bool IsEnabled { get; set; }
public bool Approved { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual ICollection<MetaData> MetaDatas { get; set; }
//MetaData
public long MetaDataId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string LanguageCode { get; set; }
public string UserId { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual ICollection<ResourceMetaList> ResourceMetaLists { get; set; }
public virtual ICollection<Resource> Resources { get; set; }
What is it that makes metadataid increase by 1 everytime i add a new metadata?
I want to be able to add metadata with a specific id?
There is a connectiontabl between resource and metadata where the id of each of them connnects them. so i want to be able to add a specific metadata for each resource but for now it gives it a new id everytime so it is not realy the same metadata that connects to each resource but a new one every time.
Edit
Maybe the problem is not how i modeled my database but how i update the database with data?
There is a connection table between Resource and MetaData and is that table i want to update rather then the metadata table itself. But the connectiontable doesn't have a model class for it self but i exists in the database. How do i update that table only?
EDIT 2
The 3 arrow point to rows that looks the same, they should all be the same row but be connected to a different resource in the table to the left.
you need to switch of the default identity mapping
Ef Fluent API mappings
or use the annotation
[DatabaseGenerated(DatabaseGenerationOption.None)]
int id {get;set;}
EF annotations
You can define and use many-to-many relationships in EF, it doesn't matter if you use a model, or Code First.
There is plenty of information on this if you google "EF many to many relationships".
If you use this EF relationship, you can let EF generate the IDs, and it will do the relationship fix-up for you, and manage the relationship table automatically.
To add / remove relationship you can use ChangeRelationshipState.
For example if you want to add relationship between Metadata 10, 12, 19 And Resource 3.
You can do like this.
using (var db = new AppContext())
{
var r3 = new Resource { ResourceId = 3 };
var m10 = new Metadata { MetadataId = 10 };
var m12 = new Metadata { MetadataId = 12 };
var m19 = new Metadata { MetadataId = 19 };
db.Entry(r3).State = EntityState.Unchanged;
db.Entry(m10).State = EntityState.Unchanged;
db.Entry(m12).State = EntityState.Unchanged;
db.Entry(m19).State = EntityState.Unchanged;
var manager = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager;
manager.ChangeRelationshipState(r3, m10, h => h.Metadatas, EntityState.Added);
manager.ChangeRelationshipState(r3, m12, h => h.Metadatas, EntityState.Added);
manager.ChangeRelationshipState(r3, m19, h => h.Metadatas, EntityState.Added);
db.SaveChanges();
}
add Key Attribute
[Key]
public long MetaDataId { get; set; }

Entity Framework and 2 associations on the same table. Is it possible?

I have 3 tables:
Product
Category
ProductCategory
when doing entity framework Database First it creates 2 classes.
Product {
int ProductId
virtual ICollection<Category>
}
Categories {
int CategoryId
virtual ICollection<Product>
}
this is great.
I want to create a new property on the table ProductCategory, for example, CreateDate
So I will have one more entity (ProductCategory) with the CreateDate property :
Product
Category
ProductCategory
So far so good, but it removes the navegability from Product to Category and creates the navegability from Product to ProductCategory and then to Category.
I manually changed in model browser and added a new navegability that create the previous behaviour in classes so I ended up with what I want :
Product {
int ProductId
virtual ICollection<Category>
ProductCategory
}
Category {
int CategoryId
virtual ICollection<Product>
ProductCategory
}
ProductCageory {
Product
Category
}
all compiles fine, but when I run the code and update an entity the following error raises :
error 3034: Problem in mapping fragments starting at lines 2445, 2452:
Two entities with possibly different keys are mapped to the same row.
Ensure these two mapping fragments map both ends of the AssociationSet
to the corresponding columns.
Is there any way to keep my desired scenario (Both navegabilities from Product to ProductCategory and to Category and vice versa) ? If so, how can I get rid of this error ? am I missing mapping something ?
Tryied to be the less verbose possible but I don't mind adding more information or updating the question if anything missing so don't hesitate to ask for more info. Thanks.
Extra: Using MySql 6.8.3 with Entity Framework 6 (latest minus version) Database First using DbContext and T4.
updated:
This is what I want to have :
public class Product
{
public int Id { get; set; }
public ICollection<ProductCategory> ProductCategories { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public ICollection<ProductCategory> ProductCategories { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
If you want to have many to many relationship that can navigate both ways, you should have as follow.
public class Product
{
public int Id { get; set; }
public ICollection<ProductCategory> ProductCategories { get; set; }
[NotMapped]
public ICollection<Category> Categories
{
get { return (ProductCategories ?? new ProductCategory[0]).Select(pc => pc.Category).ToArray(); }
}
}
public class Category
{
public int Id { get; set; }
public ICollection<ProductCategory> ProductCategories { get; set; }
[NotMapped]
public ICollection<Product> Products
{
get { return (ProductCategories ?? new ProductCategory[0]).Select(pc => pc.Product).ToArray(); }
}
}
public class ProductCategory
{
public int Id { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}

Retrieving the value in an association table in Entity Framework code first

I am using EF 4.1 code first and I am struggling with the association entity and getting the value that was set in the association table. I tried to follow the post on: Create code first, many to many, with additional fields in association table.
My tables are as follows (all are in plural form):
Table: Products
Id int
Name varchar(50)
Table: Specifications
Id int
Name varchar(50)
Table: ProductSpecifications
ProductId int
SpecificationId int
SpecificationValue varchar(50)
My related classes:
public class Product : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class Specification : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class ProductSpecification
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
My context class:
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Specification> Specifications { get; set; }
public DbSet<ProductSpecification> ProductSpecifications { get; set; }
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
}
}
My repository method where I do my call (not sure if it is correct):
public class ProductRepository : IProductRepository
{
MyContext db = new MyContext();
public Product GetById(int id)
{
var product = db.Products
.Where(x => x.Id == id)
.Select(p => new
{
Product = p,
Specifications = p.ProductSpecifications.Select(s => s.Specification)
})
.SingleOrDefault();
return null; // It returns null because I don't know how to return a Product object?
}
}
Here is the error that I am getting back:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'ProductSpecification' has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �ProductSpecifications� is based on type �ProductSpecification� that has no keys defined.
What does it mean that no keys are defined? Won't the ProductId and SpecificationId map to Id of Product and Id of Specification respectively?
How would I return a single product with the all the specifications for it?
Entity Framework will recognize that ProductId is a foreign key property for the Product navigation property and SpecificationId is a foreign key property for the Specification navigation property. But the exception complains about a missing primary key ("Key" = "Primary Key") on your ProductSpecification entity. Every entity needs a key property defined. This can happen either by conventions - by a specific naming of the key property - or explicity with data annotations or Fluent API.
Your ProductSpecification class doesn't have a property which EF would recognize as a key by convention: No Id property and no ProductSpecificationId (class name + "Id").
So you must define it explicitely. Defining it with data annotations is shown in the post you linked:
public class ProductSpecification
{
[Key, Column(Order = 0)]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
[Key, Column(Order = 1)]
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
And in Fluent API it would be:
modelBuilder.Entity<ProductSpecification>()
.HasKey(ps => new { ps.ProductId, ps.SpecificationId });
Both ways define a composite key and each of the parts is a foreign key to the Product or Specification table at the same time. (You don't need to define the FK properties explicitely because EF recognizes it due to their convention-friendly names.)
You can return a product including all specifications with eager loading for example:
var product = db.Products
.Include(p => p.ProductSpecifications.Select(ps => ps.Specification))
.SingleOrDefault(x => x.Id == id);