How to iterate through models in ModelContext from EntityFramework - c#

I have a ModelContext with hundreds of models generated with EF DB-First (Scaffold-DbContext).
How can I search through the attributes of the models for those who have a property e.g. objid generically. I could hard-code the check for each model, but that is not practical due to the number of tables / models (big enterprise DB).
Say I didn't know of the Students model below. How could I still check for a property?
using (var context = new MyDbContext())
{
var students = context.Students
.Where(s => s.objid == 1234)
.ToList();
}

Related

EntityFramework Core with one-to-many relation always returns an empty collection

I'm using an IEntityTypeConfiguration interface to configure the entities.
So, for example, I have 2 tables with names: Students / Courses.
There are configuration of tables:
Courses:
builder.HasMany(x => x.Students).WithOne().HasForeignKey(x => x.CourseId);
Students:
builder.HasOne(x => x.Course).WithMany().HasForeignKey(x => x.CourseId);
Next step is to create a test for ensure that the data is loaded correctly.
I have a repository that converts an entity to a model, but first I want to test it manually with my Context.
I have created a course and list of students, there are the students (EntityManager adds fake data to Context and save it), actually creates 100 entities by default:
var students = await EntityManager.CreateManyAsync<Student>(
x => x.CourseId = course.Id);
Then I just search for course, include the Students and test it via FluentAssertions.
var set = TestDatabase.Context.Set<Course>();
var course = await set.Include(x => x.Students).FirstAsync(x => x.Id == course.Id);
course.Students.Should().NotBeEmpty();
Entity is here, but collection is empty (but not null, given that I did not initialize it).
For unit tests I'm use the actual Context and InMemoryDatabase.
Entity collection:
public virtual ICollection<Student> Students { get; set; }
What's wrong I do?
P.S. There are no errors in the EntityManager, the relation ids is set correctly.
I removed the relation from the Students table and changed WithOne() in the Course configuration to WithOne(x=>x.Course), and now it works completely. Thanks to #Uriil

AutoMapper.Collections.EntityFramework

Asking same question differently!
Its seems clear I need to elaborate on this question because I have no viable responses.
Based on this AutoMapper registration code:
Mapper.Initialize(cfg =>
{
cfg.AddCollectionMappers();
cfg.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<DbContext>>();
});
AutoMapper adds support for "updating" DbSet collections using this line:
Mapper.Map<List<DTO>, List<Entity>>(dtoCollection, entityCollection);
Saving changes through an open context should result in updating the database:
using (var context = factory.CreateContext())
{
Mapper.Map<List<DTO>, List<Entity>>(dtoCollection, await
context.dbSet.TolistAsync());
await context.SaveChangesAsync();
}
This does nothing!
So back to my original question. If calling the mapper with the dto and current state of the entity collection returns an updated entity collection based on the comparison Mapping Created here:
cfg.SetGeneratePropertyMaps<GenerateEntityFrameworkPrimaryKeyPropertyMaps<DbContext>>();
produces entity collection here:
var entities = Mapper.Map<List<DTO>, List<Entity>>(dtoCollection, await
context.dbSet.TolistAsync());
Am I support to iterate the new collection and update EF manually using this new collection? Its not clear what I am suppose to do at this point? Is this what I am suppose to do with the resulting collection?
// map dto's to entities
var entities = Mapper.Map(collection, await dbSet.ToListAsync());
// add new records
var toAdd = entities.Where(e => e.Id == 0);
dbSet.AddRange(toAdd);
// delete old records
var toDelete = entities.Where(entity => collection.All(e => e.Id > 0 && e.Id != entity.Id));
dbSet.RemoveRange(toDelete);
// update existing records
var toUpdate = entities.Where(entity => collection.All(i => i.Id > 0 && i.Id == entity.Id)).ToList();
foreach (var entity in toUpdate)
{
context.Entry(entity).State = EntityState.Modified;
}
await context.SaveChangesAsync();
This is my original question. If so it seems redundant. So I feel like I am missing something.
I appreciate some useful feedback. Please help!
Thanks
EF DbSets are not collections. Basically they represent a database table and provide query and DML operations for it.
Looks like you want to synchronize the whole table with the DTO list. You can do that by loading the whole table locally using the Load or LoadAsync methods, and then Map the DTO collection to the entity DbSet.Local property. The difference with your attempts is that the Local property is not a simple list, but observable collection directly bound to the context local store and change tracker, so any modification (Add, Remove) will be applied to the database.
Something like this:
await dbSet.LoadAsync();
Mapper.Map(dtoCollection, dbSet.Local);
await context.SaveChangesAsync();

EF: Clear Collection Property without First Getting Data

I have a class called Facility. Facility has a collection property on it called Employees. I'm using the disconnected layer of EF. I want to clear the Employees collection from a specific facility, but I don't want to make two trips to the DB: (1) getting all the employees, and then (2) clearing the. How can I do this?
Here's what I've tried...
Facility f = new Facility()
{
Id = 4,
Employees = new List<Employee>()
};
context.Facilities.Attach(f);
context.Entry<Facility>(f).Collection(fac => fac.Employees).IsLoaded = true;
context.SaveChanges();
I think I'm close, but it doesn't work. Thanks for the advice.
If you want to use EF only, you're always going to need some roundtrip. In the end, EF needs to generate DELETE ... WHERE Id = x statements. How would it know the values for x without first grabbing them from the database?
But of course you can do this in a more efficient way than fetching the complete Employee objects. It's enough to get the Id values. Then you can use these Ids to create stub entities that you mark as Deleted:
var ids = context.Empoyees.Where(e => e.FacilityId == 4)
.Select(e => e.Id).ToArray();
foreach(int id in ids)
{
var emp = new Empoyee { Id = id }; // Stub entity
context.Entry(emp).State = System.Data.Entity.EntityState.Deleted;
}
context.SaveChanges();
This is pure EF. But you can also use EntityFramework.Extended. This allows you to execute a statement like
context.Empoyees.Where(e => e.FacilityId == 4)
.Delete();

EF, get entity tree

I have EF model and I would like to get list of entities names which are somehow related with parent entity.
Lets say thay I have this model.
If I choose entity "Orders" I should get as result Orders, Customers, Order_Details nad CustomerDemographics.
It there a way how to do this?
Thank you.
It is possible to get entity names this way and without reflection:
using (var context = new ModelContainer())
{
var container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
var entitySet = container.BaseEntitySets[someEntityName];
var navProperties = set.ElementType.Members.Where(member => member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty).Select(member => member.Name).ToList();
}

Complex EntityFramework query with multiple tables and many to many relationship

I'm having a heck of a time getting Entity Framework to do what I want. I'm writing a feed aggregator so I can add multiple rss feeds to a "FeedList" that will group all the individual podcasts and order by pubDate.
Classes (all but FeedListFeed have identity column called Id):
Feed (Id is identity primary key, has List Items property)
FeedItem (Id is identity primary key, as int FeedId and Feed Feed properties)
FeedList (FeedListName is string and List Feeds property)
FeedListFeed (many-to-many linking table, FeedListId and FeedId properties)
These mappings seems to work:
modelBuilder.Entity<FeedListFeed>().HasKey(x => x.FeedId).HasKey(x => x.FeedListId);
modelBuilder.Entity<FeedList>()
.HasMany(fl => fl.Feeds).WithMany(f => f.FeedLists)
.Map(t => t.MapLeftKey("FeedListId")
.MapRightKey("FeedId")
.ToTable("FeedListFeeds"));
Now what I want to do is get the latest 20 FeedListItem entries for Feeds in a FeedList given the FeedListName. I've come up with this, but is there a better way to do it? Will the query actually expand all the items, or will it be smart enough to do it on the SQL side?
var query =
from fl in ctx.FeedLists.Include("Feeds").Include("FeedItems")
from f in fl.Feeds
from fi in f.Items
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;
List<FeedItem> items = query.Take(20).ToList();
If I try to link the tables manually using the Id columns, I get the error Invalid object name 'dbo.FeedListFeeds1'. If I took out the Lists that link the tables to each other would this help? Is there some other mapping that let this work?
var query =
from fl in ctx.FeedLists
join flf in ctx.FeedListFeeds on fl.Id equals flf.FeedListId
join fi in ctx.FeedItems on flf.FeedId equals fi.FeedId
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;
Remove this line from your mapping ...
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => x.FeedId)
.HasKey(x => x.FeedListId);
... because it has 2 issues:
1) If you want a composite key you must not chain HasKey but create the key via an anonymous type:
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => new { x.FeedId, x.FeedListId });
2) (more important) This line lets EF consider FeedListFeed as an entity which it isn't in your model. The result is that EF creates a separate table for it with the name FeedListFeeds1 because FeedListFeeds is reserved as table name in your many-to-many mapping (.ToTable("FeedListFeeds")). For many-to-many mapping you don't need to create a class for the linking table. The linking table is managed by EF internally.
Edit
You can then also remove the FeedListFeed class completely of course.
For the query I would try then:
var query = from fi in ctx.FeedItems
where fi.Feed.FeedLists.Any(fl => fl.FeedListName == id)
orderby fi.PubDate descending
select fi;
I think you have all the necessary navigation properties in your model classes so that this query should be possible.

Categories