EF6 Code first navigation property with custom association - c#

I would like to use EF Code first to handle a custom association (or navigation property) that is not based on a key but on one of the property values of an entity.
My classes are these
public class MyConfig
{
public int Id { get; set; }
public int InstanceNumber { get; set; }
public bool IsValid { get; set; }
public int Code { get; set; }
}
public class MyAsset
{
public int Id { get; set; }
public string Name { get; set; }
public List<MyConfig> MyConfigs { get; set; }
public int InstanceValue { get; set; }
}
So I would like a collection of MyConfig entities on any of the MyAsset entities. When the MyAsset entity is loaded from the database, I would like it to contain a list of MyConfig-entities whose value in its "InstanceNumber" matches the InstanceValue of the MyAsset entity.
Can I do this with EF6 codefirst? I have read up on overriding the built-in conventions of EF, but I have only been able to find solutions that work with specific primary keys.
And does the above make sense at all?

Well I can see that why you would like to do this but currently your only option is to use the Include extension method and get all MyConfig instances then you need to filter the list of MyConfig instances in MyAsset instance you queried like this:
public class MyAssetRepository : IMyAssetRepository
{
public MyAsset Get(int assetId)
{
using (var context = new AssetContext())
{
var selectedAsset = context.MyAssets.Include(a => a.MyConfigs).Single(a => a.Id == assetId);
selectedAsset.MyConfigs = selectedAsset.MyConfigs
.Where(c => c.InstanceNumber == selectedAsset.InstanceValue)
.ToList();
return selectedAsset;
}
}
}
As in this example getting a MyAsset instance is encapsulated in a repository so the logic of getting a MyAsset instance with its MyConfig entities is encapsulated and hidden from the code outside of the repository.
Of course the overhead of getting all MyConfig entities will be present which could be resolved by using an Include extension method that accepts a filter argument, however it does not exist currently.

Related

Can I use Interfaces to hide fields in POCOs so Entity Framework Core does not try to find matching columns in a database?

I am working on a project in Entity Framework Core which uses POCOs to connect to a database. However, these POCOs cannot convey intent, which is necessary because of a role-based permission system. I need some way to convey roles alongside my POCO.
However, some of the requirements of the project make it difficult to use subclasses with additional fields to do this. Thus, I have been using interfaces and partial classes, as under normal circumstances, I can use an interface to control which parts of the class are being recognized.
For example, in following code I have a guest entity, with the role of who is editing the entity. Only the fields Id, Name, and Confirmed exist as columns within the database. Under most circumstances, I can cast to an IGuest to remove the "MyRole" field.
public partial class Guest : IGuest
{
public int Id { get; set; }
public string Name { get; set; }
public bool Confirmed { get; set; }
}
public partial class Guest : IRole
{
public string MyRole { get; set; }
}
public interface IGuest
{
public int Id { get; set; }
public string Name { get; set; }
bool Confirmed { get; set; }
}
public interface IRole
{
public string MyRole { get; set; }
}
However, if I try to create a DbSet of guests, it attempts to find a matching column for MyRole. I tried changing the following sets of code
public virtual DbSet<Guest> Guest { get; set; } = null!;
and
modelBuilder.Entity<Guest>(entity =>
{
entity.HasKey(e => e.Id);
});
into the following:
public virtual DbSet<IGuest> Guest { get; set; } = null!;
and
modelBuilder.Entity<IGuest>(entity =>
{
entity.HasKey(e => e.Id);
});
This results in an error
ArgumentException: The specified type '...IGuest' must be a non-interface reference type to be used as an entity type.
Is it possible to use an interface as part of a DbSet / entity to hide the MyRole field (and any others not in the IGuest interface) from Entity Framework so that it does not attempt to map it to a column?

Find a collection property using reflection then add to collection

I am trying to create a generic function to add relational records in entity framework. I am working on the following solution. Though if I am going about this all wrong could you direct me in the right direction.
I have the following to classes. Also to note this is a code first approach in entity framework.
public class MainTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MainTableID { get; set; }
public int LookupID { get; set; }
[ForeignKey("LookupID")]
public virtual LookupTable Lookups { get; set; }
}
public class LookupTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int LookupID{ get; set; }
public string LookupText { get; set; }
public virtual ICollection<MainTable> MainTable { get; set; }
}
I then have the following code that I am trying to add an relationship between MainTable and LookupTable. I am first passing the two table class types and passing the record to add and the name of the collection property.
The problem is the "collectionField" is NULL whenever I try to cast it (ICollection(MainTable)). I need to "collectionField" to become of type ICollection(MainTable) so I can add the MainTable record, unless I am going about this the wrong way.
public void AddRelationalRecord<MainTableType, LookupTableType>(MainTableType recordToAdd, string collectionFieldName)
where MainTableType: class
where LookupTableType: class
{
var collectionField = typeof(LookupTableType)
.GetProperties()
.Where(prop => prop.Name == collectionFieldName)
.FirstOrDefault();
collectionField.Add(recordToAdd);
}
From the comments I've decided to change my approach to this. If anyone is interested the below code works.
public void AddRelationalRecord(object recordToAdd, object recordWithCollection, string collectionFieldName)
{
var collectionProp = recordWithCollection.GetType().GetProperty(fieldName);
collectionProp.PropertyType.GetMethod("Add").Invoke(aaa.GetValue(recordWithCollection, null), new object[] { recordToAdd });
}

EF LINQ - How to query 2 tables associated by foreign key?

Using EF 6 code first. I have an Entity class and a Name class, and want to query a list of all Entity objects with their respective Names so that I can show on a Gridview control.
These are my class definitions:
public class Entity
{
[Key]
public int EntityKey { get; set; }
public virtual ICollection<Name> Names { get; set; }
}
public class Name
{
[Key]
public int NameKey { get; set; }
public int EntityKey { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Entity Entity { get; set; }
}
In my Data Access Layer, I have a method that gets all entities from database:
public List<Entity> GetEntities()
{
SdmDBContext entityDbContext = new SdmDBContext();
return entityDbContext.Entities.ToList();
}
but with this query, I don't get the Names that are associated to the Entity class. I have tried LINQ and lambda expressions, as well as an .Include operation, but not sure how to formulate the syntax properly.
How can I change the DAL method to return the Names associated with all existing entities?
You code is loading the list of Entities in the Entity table, and with 'lazy loading' it should also return associated Name collections when you access them. You declare your ICollection as virtual to enable 'lazy loading' because a proxy object will be created to make DB requests when you access something that hasn't been eagerly loaded.
Include is just a request to eagerly load related entities by using the navigation properties defined on the object. You can call Include using a lambda or string representing the name of the navigational property you want to eagerly load.
For example:
public List<Entity> GetEntities()
{
SdmDBContext entityDbContext = new SdmDBContext();
return entityDbContext.Entities.Include("Names").ToList();
}

Generic classes in EF Code First

I’m trying to incorporate EF in my application. I use EF6 and Code First. My object model has some generic classes and it seems that Code First accepts it partially (tables and columns are generated correctly).
Below an example:
public class Group : TimeableObject<GroupTimeline> {}
public class GroupTimeline : TimelineBase
{
public GroupTimeline() { }
public string Name {get; set;}
}
public abstract class TimelineBase
{
public int Id { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
public abstract class TimeableObject<T>
where T : TimelineBase
{
private DateTime? _snapshotDate;
public TimeableObject()
{
Timelines = new List<T>();
}
public int Id { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
public List<T> Timelines { get; set; }
public DateTime SnapshotDate
{
get { //some logic.. }
set { _snapshotDate = value; }
}
public T Timeline
{
get { //some logic.. }
}
}
Through fluent API I configured the inheritance as TPC. So I expect a table Groups and a table GroupTimeline with a foreign key to Groups in GroupTimeline.
First of all, the generated database looks correct except for the identity. The strange thing about this is that EF sees the Id property in e.g. Group and generates a PK in the database. Althought without an identity. The same for the Id property in the TimelineBase class.
Second, I don't want the property SnapshotDate to be mapped. Through fluent API I was not able to reach this. Here I get an exception telling me that the complete class is ignored or is a generic. So I assume that EF does not support generic classes although the database is generated correctly (not the identity). What's the story behind this??
With the use of data annotations I fixed my issues. I can get an identity with the attributes (on the Id property):
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
and I can prevent mapping the SnapshotDate property with:
[NotMapped]
below my fluent configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>().Map(m =>
{
m.MapInheritedProperties();
});
modelBuilder.Entity<GroupTimeline>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Group_Timeline");
});
modelBuilder.Entity<TimeableObject<TimelineBase>>().HasKey(m => m.Id).Property( p => p.Id ).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<TimeableObject<TimelineBase>>().Ignore(m => m.SnapshotDate);
modelBuilder.Entity<TimelineBase>().HasKey(m => m.Id).Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
This throw's an exception:
The type 'Data.DataObjectModel.TimeableObject`1[Data.DataObjectModel.TimelineBase]'
was not mapped. Check that the type has not been explicitly excluded by using the
Ignore method or NotMappedAttribute data annotation. Verify that the type was
defined as a class, is not primitive or generic, and does not inherit from
EntityObject.
Regards,
Daniel Kaya

EF6 does not lazy load navigation property

I'm having an issue with EF6 lazy loading. I've searched StackOverflow, but the other questions I've found doesn't fit my case.
I'm using the virtual keyword and my classes are public. LazyLoadingEnabled and ProxyCreationEnabled are both set to true.
When I load a course object from the db, presentationId is set to the correct id and presentation is null which is correct, because it hasn't been loaded yet.
When I pass the presentation property to the PresentationsController.ToDto() method it should be lazy loaded, but I get a null reference exception inside the method because it's still null.
I know that the relationships are working because when I force load the presentation property of a course in the Watch window with a break point at the public static CourseDto ToDto(Course item, DnbContext db) method it gets loaded. See images:
As you can see item.presentation is null:
When I manually evaluate db.courses.Find(257).presentation which is referencing the same presentation as the item object does, they're both loaded:
Here are my POCOs:
public abstract class BaseModel : ISoftDelete {
public int id { get; set; }
}
public class Course : BaseModel {
[Required]
public int presentationId { get; set; }
public virtual Presentation presentation { get; set; }
}
My Web API controller methods:
// GET api/Courses/5
public CourseDto GetCourse(int id) {
var item = db.courses.FirstOrDefault(x => x.id == id);
return ToDto(item, db);
}
public static CourseDto ToDto(Course item, DnbContext db) {
var dto = new CourseDto();
if (item.presentationId > 0) dto.presentation = PresentationsController.ToDto(item.presentation, db);
return dto;
}
Any ideas?
Entities must have explicitly declared public constructors if you want to use lazy loading via dynamic proxies. (If you have other with parameters)
public abstract class BaseModel : ISoftDelete {
public BaseModel() { }
public int id { get; set; }
}
public class Course : BaseModel {
public Course() { }
[Required]
public int presentationId { get; set; }
public virtual Presentation presentation { get; set; }
}

Categories