Assuming we have the following db
and we have the following classes
class Context : DbContext
{
public DbSet<Control> Control { get; set; }
public DbSet<LanguageResource> LanguageResource { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<LanguageResource>()
.HasKey(l => new { l.LanguageResourceId, l.CultureCode });
modelBuilder.Entity<Control>()
.HasKey(c => c.ControlId);
}
}
class Control
{
public int ControlId { get; set; }
public int LanguageResourceId { get; set; }
public ICollection<LanguageResource> LanguageResources { get; set; }
}
class LanguageResource
{
public int LanguageResourceId { get; set; }
public string CultureCode { get; set; }
public string Text { get; set; }
}
Is there a way to configure the Context to relate Control.LanguageResourceId to LanguageResource.LanguageResourceId to populate Control.LanguageResources.
I'm thinking of doing something like this.. but obviously it doesn't work.. :D
var control =
context
.Control
.Include(c => c.LanguageResources)
.First();
/// {
/// ControlId : 1
/// LanguageResources : [
/// {
/// LanguageResourceId : 1,
/// CultureCode : en-US
/// Text : Hello
/// },
/// {
/// LanguageResourceId : 1,
/// CultureCode : en-CA
/// Text : Hi
/// }
/// ]
/// }
I only have read-only access to the database so modifying the table/fields is out of the question. :)
We're using EF6 but I'd like to know if this can be done in EF Core also. :)
Related
Scenario
public class Product : Entity, IAggregateRoot
{
public string Name { get; set; }
public string Dimension { get; set; }
public decimal Volume { get; set; }
public bool Featured { get; set; }
public Farm Farm { get; set; }
public int FarmId { get; set; }
/// <summary>
/// Sell Price
/// </summary>
public decimal BidPrice { get; set; }
public int QuantityAvaliable { get; set; }
public ICollection<Image> Images { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class Category : Entity, IAggregateRoot
{
public string Title { get; set; }
public string CategoryImage { get; set; }
public Category Parent { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
Relationship setup
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Created).HasColumnType("DateTime");
Property(x => x.Modified).HasColumnType("DateTime");
Property(x => x.BidPrice).HasColumnType("Decimal");
#region RELATIONSHIP
//BelongsTo
HasRequired(x => x.Farm);
HasRequired(x => x.Category);
HasMany(x => x.Images);
#endregion
}
So I have this two model where I need to bring the data from Product model with Category information
I have checked my database, the data is consistent, the Product record have the FK for the Category record.
but when I try to get Product Data using EF6, the category information doesnt come, I get a null object.
Because of = () =>
{
_product = _repository.Find(p => p.Id == 1, p => p.Category);
};
It should_not_be_bull = () =>
_product.Category.ShouldNotBeNull();
the response from data base is for Category is null. but the record is there.
I had it working properly before. for some random magic reason it just stop working.
THE FIND method
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate = null, params Expression<Func<TEntity, object>>[] includes)
{
var set = CreateIncludedSet(includes);
return (predicate == null) ?
set.FirstOrDefault() :
set.FirstOrDefault(predicate);
}
the CreateIncludeSet
private IDbSet<TEntity> CreateIncludedSet(IEnumerable<Expression<Func<TEntity, object>>> includes)
{
var set = CreateSet();
if (includes != null)
{
foreach (var include in includes)
{
set.Include(include);
}
}
return set;
}
the CreateSet method
private IDbSet<TEntity> CreateSet()
{
return Context.CreateSet<TEntity>();
}
MY DbContext implementation is here
https://github.com/RobsonKarls/FreedomWebApi/blob/dev/Source/Freedom.Infrastructure.DataAccess/Factories/FreedomDbContext.cs
all project is there too for further analisys
any help is valuable.
Thank you
The problem in your code is in this line in CreateIncludedSet method:
set.Include(include);
Yes, you include the data but you do not change you set. You should change it to something like:
set = set.Include(include);
Your code is a bit unclear, but try something like this....
_product = _repository.Include(p => p.Category).SingleOrDefault(x => x.Id == 1);
also see...
https://stackoverflow.com/a/7348694/6200410
This question already has answers here:
A specified Include path is not valid. The EntityType does not declare a navigation property with the name *
(5 answers)
Closed 3 years ago.
I am getting below error while trying to access below method,
public ZipCode GetByZip(string zip)
{
using (GeoLibDbContext entityContext = new GeoLibDbContext())
{
return entityContext.ZipCodeSet.Include(e => e.StateId).FirstOrDefault(e => e.Zip == zip);
}
}
Below are the POCO and db context classes,
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
}
public class ZipCode
{
public int ZipCodeId { get; set; }
public string City { get; set; }
public int StateId { get; set; }
public string Zip { get; set; }
public string County { get; set; }
public State State { get; set; }
}
public class HelloLibDbContext : DbContext
{
public HelloLibDbContext()
: base("name=HelloLibDbContext")
{
Database.SetInitializer<HelloLibDbContext>(null);
}
public DbSet<ZipCode> ZipCodeSet { get; set; }
public DbSet<State> StateSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ZipCode>().HasKey<int>(e => e.ZipCodeId)
.HasRequired(e => e.State).WithMany().HasForeignKey(e => e.StateId);
modelBuilder.Entity<State>().HasKey<int>(e => e.StateId);
}
}
If I removed ".Include(e => e.StateId)", then I m getting any error, but at the same time "State" property is null, which defines in "ZipCode" class.
What could be the reason? Please suggest!!!
You should include State. Not StateId
public ZipCode GetByZip(string zip)
{
using (GeoLibDbContext entityContext = new GeoLibDbContext())
{
return entityContext.ZipCodeSet.Include(e => e.State).FirstOrDefault(e => e.Zip == zip);
}
}
Include works with navigation property, and in your case navigation property is State.
I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}
[Table("Employee", Schema = "Master")]
public class Employee : Common
{
#region Properties
[Required]
[Key]
public int EmployeeID { get; set; }
public virtual Department Department { get; set; }
public int? DepartmentId { get; set; }
#endregion
}
[Table("Department", Schema = "Lookup")]
public class Department : Common
{
[Required]
[Key]
public int DepartmentId { get; set; }
[Required]
[StringLength(50)]
public string Value { get; set; }
public string Description { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
To get data
var employee = CemexDb.Employee.Where(w => w.EmployeeID == employeeId).FirstOrDefault();
when I fetch data, department always null
Please suggest the workaround
Here my context class that is in my code context class
public class CemexDb : DbContext
{
public virtual IDbSet<T> DbSet<T>() where T : class
{
return Set<T>();
}
public CemexDb() : base(ConfigurationManager.ConnectionStrings["CemexDb"].ConnectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
}
public CemexDb(string connectionString): base(connectionString)
{
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
}
public virtual void Commit()
{
base.SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<CemexDb>(null);
}
}
here is the access code
public class EmployeeService : RepositoryBase, IEmployeeService
{
public EmployeeService(IDatabaseFactory DbFactory): base(DbFactory)
{ }
}
Repository base class
public abstract class RepositoryBase
{
private CemexDb db;
/// <summary>
/// Holds a reference to the DatabaseFactory class used to manage connections to the database.
/// </summary>
protected IDatabaseFactory DatabaseFactory { get; private set; }
/// <summary>
/// Contains a reference to the <see cref="System.Data.Entity.DbContext"/> instance used by the repository.
/// </summary>
protected CemexDb CemexDb { get { return db ?? (db = DatabaseFactory.Get()); } }
/// <summary>
/// Initialises a new instance of the RepositoryBase class.
/// </summary>
/// <param name="DbFactory">A valid DatabaseFactory <see cref="Opendesk.Data.DatabaseFactory"/> object.</param>
public RepositoryBase(IDatabaseFactory DbFactory)
{
DatabaseFactory = DbFactory;
}
}
You must have proxy generation enabled and collection properties defined as virtual in order to have lazy loading working.
Also context should stay alive.
public class CemexDb : DbContext
{
public CemexDb()
{
this.Configuration.ProxyCreationEnabled = true;
}
public DbSet<Unicorn> Employees { get; set; }
}
This is in reference to the question I asked regarding how to determine when items are added to the virtual ICollection property. As suggested, I have created a custom collection which inherits from Collection as shown below
public class EntityCollection<T> : Collection<T>
{
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
}
}
This is being used as
public class DbAppointment
{
public DbAppointment()
{
exceptionOcurrences = new EntityCollection<DbExceptionOcurrence>();
}
public virtual int AppointmentId { get; set; }
public virtual string Subject { get; set; }
public virtual string Body { get; set; }
public virtual DateTime Start { get; set; }
public virtual DateTime End { get; set; }
private ICollection<DbExceptionOcurrence> exceptionOcurrences;
public virtual ICollection<DbExceptionOcurrence> ExceptionOcurrences
{
get { return exceptionOcurrences; }
set { exceptionOcurrences = value; }
}
}
The problem is the only time the overridden InsertItem method seems to get called is if I initialise the database with a custom initialiser (example code below) and override the seed method!! What am I doing wrong?
Cheers
Abs
public class ContextInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
new List<DbAppointment>
{
new DbAppointment{ Subject = "hello", Body="world", Start=DateTime.Now, End=DateTime.Now.AddMinutes(30)},
}.ForEach(a => context.Appointments.Add(a));
new List<DbExceptionOcurrence>
{
new DbExceptionOcurrence{ExceptionDate=DateTime.Now}
}.ForEach(eo => context.ExceptionOcurrences.Add(eo));
base.Seed(context);
}
}