I am faced with an EF6 Code First context, with a few DbSets of POCOs that have navigation properties (and foreign keys) between them, e.g.:
public partial class Person
{
public Guid Id { get; set; }
public virtual ICollection<Address> Address { get; set; }
}
public partial class Address
{
public Guid Id { get; set; }
public Guid FK_PersonId { get; set; }
public virtual Person Person { get; set; }
}
modelBuilder.Entity<Person>()
.HasMany (e => e.Address)
.WithRequired (e => e.Person)
.HasForeignKey (e => e.FK_PersonId)
.WillCascadeOnDelete(false);
Given these types, is there any proper way (i.e. without resorting to iterating over the POCO properties/fields by reflection and "guessing") to programmatically determine that Address has an FK_PersonId pointing to the Id property of Person?
To get the FK property's names for an specific entity you can use this generic method:
public IEnumerable<string> GetFKPropertyNames<TEntity>() where TEntity:class
{
using (var context = new YourContext())
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
var Fks = set.EntitySet.ElementType.NavigationProperties.SelectMany(n=>n.GetDependentProperties());
return Fks.Select(fk => fk.Name);
}
}
And if you want the nav. property's names the only you need to do is this:
//...
var navProperties = set.EntitySet.ElementType.NavigationProperties.Select(np=>np.Name);
Related
I'm learning C# at the moment with .NET Core and EF Core for working with database.
Now I'm at the point where I got stuck configuring my entities.
I have written the following classes:
public class Customer
{
#region Properties
public Guid CustomerID { get; set; }
...
public Address Address { get; set; }
#endregion
public Customer()
{
Address = new Address();
}
}
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
//Primary Key
builder.HasKey(c => c.CustomerID);
//Complex Types
builder.OwnsOne<Address>("Address");
}
}
public class Employee
{
#region Properties
public Guid EmployeeID { get; set; }
...
public Address Address { get; set; }
#endregion
public Employee()
{
Address = new Address();
}
}
public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
//Primary Key
builder.HasKey(c => c.EmployeeID);
//Complex Types
builder.OwnsOne<Address>("Address");
}
}
public class Address
{
public string Street { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public State State { get; set; }
public Country Country { get; set; }
}
I need to configure Address class with Fluent API that Address.Street is for Customer and Employee MaxLength = 50?
Is it possible to configure it for both at the same time? Or do I need to configure it for each entity?
Thanks for your help!
The valid answer of #Gert Arnold demonstrates, how to accomplish what you want for all of your target entities in a centralized way.
In case you want to keep the information in your configuration classes, then you can define it there instead (but it could be a bit more redundant, depending on the case):
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
//Primary Key
builder.HasKey(c => c.CustomerID);
//Complex Types
builder.OwnsOne(e => e.Address)
.Property(e => e.Street)
.HasMaxLength(50);
}
}
public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
//Primary Key
builder.HasKey(c => c.EmployeeID);
//Complex Types
builder.OwnsOne(e => e.Address)
.Property(e => e.Street)
.HasMaxLength(42); // or 50 if you want
}
}
Until the current stable version of Entity Framework Core (3.1.6) this is not possible.
Quote:
Instances of owned entity types cannot be shared by multiple owners
(this is a well-known scenario for value objects that cannot be
implemented using owned entity types)
Source: https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities#current-shortcomings.
Your only option here would be to make Address an Entity itself with its own table.
Then you would have an many-to-one relation from Customer / Employee (many) to Address (one).
For this you would have to extend your Address with a primary key and Customer and Employee with a foreign key each referencing Address. Then as you would do normally you can create a TypeConfiguration with your validation rules.
You can set all kinds of attributes of model types at once because they're easily accessible by modelBuilder.Model.GetEntityTypes(). Setting max length of owned type properties can be done like so:
var ownedAddresses = modelBuilder.Model.GetEntityTypes()
.Where(t => t.IsOwned() && t.ClrType == typeof(Address))
.ToList();
foreach (var address in ownedAddresses)
{
address.FindProperty("Street").SetMaxLength(50);
}
...after the owned types have been registered to the model builder, i.e. after you added the type configurations.
Of course, the Where(t => t.IsOwned() predicate is redundant here. It's just to show another way to find owned types.
In Entity Framework, I have created a Department class which has an auto-incremented DepartmentId, and ICollection of Employees.
I don't want anyone see these two properties while creating the object of Department class. Can anyone help?
I tried using access modifier but that didn't work.
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public ICollection<Employee> Employees { get; set; }
}
using (var ctx = new EmployeeContext())
{
var department = new Department()
{
DepartmentName = "Engineering",
};
ctx.Departments.Add(department);
ctx.SaveChanges();
}
When a user is doing
var department = new Department()
then department.DepartmentId and department.Employees should not be available.
If I understand correctly when you want to achieve is abstracting the POCO Department Class.
Well you can try creating a ViewModel for example:
public class DepartmentViewModel
{
public string DepartmentName { get; set; }
}
Then Show or Get Database base this ViewModel, However if you want to Get the POCO Object from your for example : Dbset.Find() method or vice versa, you need to fill the object base on other object OR use Mappers such as AutoMapper. lets say you have an object from DepartmentViewModel called vm like below:
var vm = new DepartmentViewModel(); //<---- this is an example
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<DepartmentViewModel, Department>();
});
IMapper mapper = config.CreateMapper();
//entity is a POCO Department Object.
var entity = mapper.Map<DepartmentViewModel, Department>(vm);
//here you can pass the entity to EF for insert.
using (var ctx = new EmployeeContext())
{
ctx.Departments.Add(entity);
ctx.SaveChanges();
}
Then as explained you can easily map it using Automapper, as you can see entity is now your Department object and you can do the vice versa by changing the DepartmentViewModel with Department in config.
And don't forget to download AutoMapper from nuget:
Install-Package AutoMapper -Version 8.1.1
You can use backing fields feature.
Entity framework core
public class Department
{
private int _departmentId { get; set; }
public string DepartmentName { get; set; }
private ICollection<Employee> _employees { get; set; }
}
public class Employee
{
private int _employeeId { get; set; }
public Department Department;
}
and now in your DbContext class
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().HasKey("_departmentId");
modelBuilder.Entity<Department>().Property<int>("_departmentId");
modelBuilder.Entity<Department>(c =>
c.HasMany(typeof(Employee), "_employees")
.WithOne("Department")
);
modelBuilder.Entity<Employee>().HasKey("_employeeId");
base.OnModelCreating(modelBuilder);
}
Entity framework 6.2.0
public class Employee
{
private int _employeeId { get; set; }
public Department Department { get; set; }
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(d => d._employeeId);
}
}
}
public class Department
{
private int _departmentId { get; set; }
public string DepartmentName { get; set; }
private ICollection<Employee> _employees { get; set; }
public class DepartmentConfiguration : EntityTypeConfiguration<Department>
{
public DepartmentConfiguration()
{
HasKey(d => d._departmentId);
Property(d => d._departmentId);
HasMany(d => d._employees).WithOptional(e => e.Department);
}
}
}
and now in your DbContext class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations
.Add(new Department.DepartmentConfiguration())
.Add(new Employee.EmployeeConfiguration());
base.OnModelCreating(modelBuilder);
}
I had tried this code and it works.
Backing fields are an option, however I don't recommend them for PKs given you are likely going to want to be able to select data by PK and the PK won't be exposed by the entity. For instance you might want to check child data using:
var employeesInDepartment = context.Employees
.Where(x => x.Department.DepartmentId == departmentId)
.ToList()
or
var employeesInDepartment = context.Departments
.Where(x => x.DepartmentId == departmentId)
.SelectMany(x => x.Employees)
.ToList();
This won't be available if your Department ID is completely hidden. You can leverage .Find which will use the mapped PK, however, this is less practical because Find will always load the entity, where Select can be more selective about the data you want to retrieve. You'll also want PKs to send to the UI so that you can associate data coming back to their respective entities on the return trip. For instance when I create a new employee, it will be associated to a department. I send back a DepartmentID to associate, not disconnected entities. Sending entities is a performance and security trap, and even if you did want to serialize entities across, private backing fields would not be transferred so a Department entity coming back would lack any PK, rendering it useless.
Instead of trying to hide the property, you can guard the Setters by making them Private.
public class Department
{
public int DepartmentId { get; private set; }
// ...
public virtual ICollection<Employee> Employees { get; private set; } = new List<Employee>();
}
This way you can still take advantage of accessing these properties in Linq expressions without implying they can be set. Collection references should never expose setters to avoid issues where someone tries clearing a child collection by setting the collection to a new List.
Note: This solution still will not allow you to receive entities from the client because the private setter will prevent deserialization. This is a good thing, as it will help ensure that your project does get corrupted to accept entities from the client. I cover the implications of this here.
I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem
If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.
I have to copy a collection of recodrs and add them to a db with new Ids.
var subEntities= ct.SubEntities.Where(qf => qf.ParentEntityId == oldParentEntityId).ToList();
subEntities.ForEach(qf => { qf.ParentEntityId = newParentEntityId; qf.Id = default(int); });
ct.SubEntities.AddRange(subEntities);
When AddRange has run all entities subEntities has awkward Ids like -2147482647 and they go into db though there is a correct sequence. How to fix it?
My entity classes and mapping:
public class SubEntity
{
public int Id { get; set; }
public Guid ParentEntityId { get; set; }
public virtual ParentEntity ParentEntity { get; set; }
//props
}
public class ParentEntity
{
public Guid Id { get; set; }
public virtual List<SubEntity> SubEntities { get; set; }
//props
}
//OnModelCreating
builder.Entity<ParentEntity>()
.HasMany(q => q.SubEntities)
.WithOne(qf => qf.ParentEntity)
.HasForeignKey(qf => qf.ParentEntityId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade);
The problem was the way the ParentEntity were extracted. It were tracked by EF (so did the SubEntities too I suppose), so I tried to add a collection already being tracked. I don't quite understand how EF works in this case but the solution was:
var subEntities= ct.SubEntities
.Where(qf => qf.ParentEntityId == oldParentEntityId)
.AsNoTracking()
.ToList();
I made a small project with Northwind database to illustrate the problematic.
Here is the action of the controller :
[HttpPost]
public ActionResult Edit(Product productFromForm)
{
try
{
context.Products.Attach(productFromForm);
var fromBD = context.Categories.Find(productFromForm.Category.CategoryID);
productFromForm.Category = fromBD;
context.Entry(productFromForm).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
context is instanced in the constructor of the Controller as new DatabaseContext().
public class DatabaseContext:DbContext
{
public DatabaseContext()
: base("ApplicationServices") {
base.Configuration.ProxyCreationEnabled = false;
base.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Configurations.Add(new ProductConfiguration());
modelBuilder.Configurations.Add(new CategoriesConfiguration());
}
private class ProductConfiguration : EntityTypeConfiguration<Product> {
public ProductConfiguration() {
ToTable("Products");
HasKey(p => p.ProductID);
HasOptional(p => p.Category).WithMany(x=>x.Products).Map(c => c.MapKey("CategoryID"));
Property(p => p.UnitPrice).HasColumnType("Money");
}
}
private class CategoriesConfiguration : EntityTypeConfiguration<Category> {
public CategoriesConfiguration() {
ToTable("Categories");
HasKey(p => p.CategoryID);
}
}
}
public class Category {
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product {
public int ProductID { get; set; }
public string ProductName { get; set; }
public string QuantityPerUnit { get; set; }
public decimal UnitPrice { get; set; }
public Int16 UnitsInStock { get; set; }
public Int16 UnitsOnOrder { get; set; }
public Int16 ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public virtual Category Category { get; set; }
}
The problem is that I can save anything from the Product but not the change of the category.
The object productFromForm contains the new CategoryID inside productFromForm.Product.ProductID without problem. But, when I Find() the category to retrieve the object from the context I have an object without Name and Description (both stay to NULL) and the SaveChanges() doesn't modify the reference even if the ID has changed for the property Category.
Any idea why?
Your (apparently) changed relationship doesn't get saved because you don't really change the relationship:
context.Products.Attach(productFromForm);
This line attaches productFromForm AND productFromForm.Category to the context.
var fromBD = context.Categories.Find(productFromForm.Category.CategoryID);
This line returns the attached object productFromForm.Category, NOT the object from the database.
productFromForm.Category = fromBD;
This line assigns the same object, so it does nothing.
context.Entry(productFromForm).State = EntityState.Modified;
This line only affects the scalar properties of productFromForm, not any navigation properties.
Better approach would be:
// Get original product from DB including category
var fromBD = context.Products
.Include(p => p.Category) // necessary because you don't have a FK property
.Single(p => p.ProductId == productFromForm.ProductId);
// Update scalar properties of product
context.Entry(fromBD).CurrentValues.SetValues(productFromForm);
// Update the Category reference if the CategoryID has been changed in the from
if (productFromForm.Category.CategoryID != fromBD.Category.CategoryID)
{
context.Categories.Attach(productFromForm.Category);
fromBD.Category = productFromForm.Category;
}
context.SaveChanges();
It becomes a lot easier if you expose foreign keys as properties in the model - as already said in #Leniency's answer and in the answer to your previous question. With FK properties (and assuming that you bind Product.CategoryID directly to a view and not Product.Category.CategoryID) the code above reduces to:
var fromBD = context.Products
.Single(p => p.ProductId == productFromForm.ProductId);
context.Entry(fromBD).CurrentValues.SetValues(productFromForm);
context.SaveChanges();
Alternatively you can set the state to Modified which would work with FK properties:
context.Entry(productFromForm).State = EntityState.Modified;
context.SaveChanges();
The problem is that EF tracks association updates differently than value types. When you do this, context.Products.Attach(productFromForm);, the productFromForm is just a poco that doesn't track any changes. When you mark it as modified, EF will update all value types, but not associations.
A more common way is to do this:
[HttpPost]
public ActionResult Edit(Product productFromForm)
{
// Might need this - category might get attached as modified or added
context.Categories.Attach(productFromForm.Category);
// This returns a change-tracking proxy if you have that turned on.
// If not, then changing product.Category will not get tracked...
var product = context.Products.Find(productFromForm.ProductId);
// This will attempt to do the model binding and map all the submitted
// properties to the tracked entitiy, including the category id.
if (TryUpdateModel(product)) // Note! Vulnerable to overposting attack.
{
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
The least-error prone solution I've found, especially as models get more complex, is two fold:
Use DTO's for any input (class ProductInput). Then use something like AutoMapper to map the data to your domain object. Especially useful as you start submitting increasingly complicated data.
Explicitly declare foreign keys in your domain objects. Ie, add a CategoryId do your product. Map your input to this property, not the association object. Ladislav's answer and subsequent post explain more on this. Both independent associations and foreign keys have their own issues, but so far I've found the foreign key method to have less headaches (ie, associated entities getting marked as added, order of attaching, crossing database concerns before mapping, etc...)
public class Product
{
// EF will automatically assume FooId is the foreign key for Foo.
// When mapping input, change this one, not the associated object.
[Required]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}