The Task pattern says that in order to be consistent everything has to be completely async or completely not async.
By using entity framework designer first I can achieve this quite easily
var course = await db.Courses.FindAsync(CourseID);
Courses is a DbSet generated by entity framework and therefore has all the Async methods.
The problem is that if I add a navigation property to that class, the latter is not a DbSet and it does not contain any async method definition.
As an example, if I add a navigation property to a Students table, it will be created as a virtual ICollection Students
which means I cannot use the async methods.
But what I do want is having entity framework to automatically generate a Task<> in order to be able to await even the navigation properties.
Is It possible? my goal is to achieve something like this:
var course = await db.Courses.FindAsync(CourseID);
var student = await course.Students.FindAsync(StudentID);
while at the moment my options are mixing async/notAsync code:
var course = await db.Courses.FindAsync(CourseID);
var student = course.Students.First(p => p.ID = StudentID);
or not using the navigation property at all:
var course = await db.Courses.FindAsync(CourseID);
var student = await db.Students.Where(p => p.CourseID == course.ID && p.ID == StudentsID).FirstAsync();
Can you suggest a solution that do not require code first?
EDIT
according to https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF.#AsyncLazyLoading what im searching for is called "Async Lazy Loading" and is not a feature available yet (and maybe it will never be).
It seems that you can either use Lazy Loading OR the async features, maybe I should just wrap the property in a Task by awaiting Task.Run(course.Students.First(p => p.ID = StudentID)) but I'm not sure it is a good idea.
In my opinion, lazy loading (which would in my opinion be the case where enumerating the navigation property would trigger a database access) is a bad access pattern, as it simply means that database access will happen at surprising places, which can make application performance difficult to predict.
All the solutions below use an import from System.Data.Entity.
Solution 1: Use eager loading with an Include
var course = await db.Courses.Include(c => c.Students).FirstOrDefaultAsync(c => c.ID == CourseID);
var student = course.Students.First(p => p.ID == StudentID);
Advantages:
One database access that is required to load all objects - and this solution scales very well if you want to retrieve more than one Course object at a time;
The Students navigation property is loaded and can be used freely;
Drawbacks:
There is always at least one database access;
The whole set of related Student objects is loaded even if you needed only one;
Solution 2: Use the LoadAsync method that exists on the concrete collection class;
This solution relies on the fact that lazy-loaded collections are from the EntityCollection<TEntity> class.
First, I would define an extension method:
public static async Task LoadAsync<T>(ICollection<T> collection)
where T : class
{
if (collection == null) throw new ArgumentNullException("collection");
var entityCollection = collection as System.Data.Entity.Core.Objects.DataClasses.EntityCollection<T>;
if (entityCollection == null || entityCollection.IsLoaded) return;
await entityCollection.LoadAsync(CancellationToken.None).ConfigureAwait(false);
}
Then you could write something like:
var course = await db.Courses.FindAsync(CourseID);
await course.Students.LoadAsync();
var student = course.Students.First(p => p.ID = StudentID);
Advantage:
There may be no database access at all if the objects are already loaded in the context;
The navigation property Students is guaranteed to be loaded;
Drawbacks:
Susceptible to the "N+1 queries" issue;
Both the Course and the set of related Student objects can grow stale, which may trigger concurrency issues down the road; (note that concurrency issues that affect a relationship are harder to resolve than concurrency issues that affect a single record)
Solution 3: Use the CreateSourceQuery method on the concrete class to load only the Student object that you want.
OK, doing that does not work, and actually is a pretty bad idea.
However, a solution with the same advantages/drawbacks can be written, but in another way:
var course = await db.Courses.FindAsync(CourseID);
var studentsQuery = from c in db.Courses
where c.ID == CourseID
from s in c.Students
select s;
var student = await studentsQuery.FirstAsync(p => p.ID = StudentID);
Advantage:
You only load the one Student object that you are going to use;
Drawbacks:
The Students navigation property is not loaded, meaning that it cannot be used without potentially triggering a database access;
The second line will always trigger a database access (susceptible to the "N+1 queries" issue, or even running the method times);
Solution 4: Eager loading, more selective: load both the course and the student that interest you in the initial LINQ query.
I am not 100% sure that that solution will work as written.
var query = from c in db.Courses
where c.ID == CourseID
select new { course = c, student = c.Students.First(p => p.ID == StudentID) };
var result = await query.FirstOrDefaultAsync();
var course = result.course;
var student = result.student;
Advantages:
Only one database access is required to retrieve both objects;
You only retrieve the objects that you are going to work on;
Drawbacks:
The Students navigation property is not loaded, meaning that it cannot be used without potentially triggering a database access;
** When to use which solution? **
If you need the navigation property to be filled (either because you know that you will make use of most of its elements, or because you want to pass the parent entity to another component that is allowed to make use of that property however it wants), then use solution 1 or 2;
If you do not need the navigation property to be filled, then use solution 4. Only use solution 3 if you categorically have the Course object already loaded;
Related
I am trying to do some checks on my database with an automated process. On a schedule the process goes out to a service and checks all the entries in the database against a list.
I want to re-insert the records that may have been deleted and update ones that are out of date
foreach (Category x in CustomeClass)
{
Category exists = Context.SSActivewear_Category
.Where(b => b.CategoryID == x.CategoryID)
.FirstOrDefault();
if (exists == null)
Context.Add(x);
else
Context.Update(x);
}
Not sure but I keep getting messages about tracking an instance with the same key etc. Can someone point me to a best practice on something like this
Danka!
This type of error is common when re-using the same instance of EF dbContext, especially when trying to load the same entity more then once from database using the same context.
If this is the case, then simply recreate the context (either new it up or use context factory) and then try update or modify your database data.
Creating new context is cheap, so no worries there.
After updating, save changes and dispose of the context (or use it in using statement to begin with).
If you are modifying the same entity multiple times using the same context, then do not load it from database multiple times.
In Your particular code example I would check if there is no duplication of Category objects in CustomeClass collection.
Is there duplication of CategoryID?
Is CategoryID for sure auto-generated when saving data (entity configuration)?
Is code not trying to update multiple entities with same id?
etc.
Entity framework works with references. Two instances of a class with the same data amount to two different references and only one reference can be associated with a DbContext, otherwise you get errors like that.
As per your example below:
foreach (Category x in CustomeClass)
{
Category exists = Context.SSActivewear_Category
.Where(b => b.CategoryID == x.CategoryID)
.FirstOrDefault();
if (exists == null)
Context.Add(x);
else
Context.Update(x);
}
Category is assumed to be your Entity, so "CustomeClass" would be a collection of instances that are not associated with your Context. These are Detached instances.
When your "exists" comes back as #null, this will appear to work as the Category "x" gets added and tracked by the Context. However, when "exists" comes back as not #null, you now have two instances for the same entity. "exists" is tracked by the DbContext, while "x" is not. You cannot use Update() with "x", you must copy the values across.
The simplest way to do this would be Automapper where you can create a map from Category to Category, then use Map to copy all values from "x" over into "exists":
var config = new MapperConfiguration(cfg => cfg.CreateMap<Category, Category>());
var mapper = config.CreateMapper();
mapper.Map(x, exists);
This is purely an example, you'll probably want to configure and inject a mapper that handles your entity copying. You can configure the CreateMap to exclude columns that shouldn't ever change. (Using ForMember, etc.)
Alternatively you can copy the values across manually:
// ...
else
{
exists.Name = x.Name,
exists.SomeValue = x.SomeValue,
// ...
}
In general you should avoid using the Update method in EF as this will result in a statement that overwrites all columns in a table, rather than updating just the column(s) that changes. (If no columns changed then no UPDATE SQL will actually get run)
On another side note, when getting "exists", you should use SingleOrDefault() not FirstOrDefault() as you expect 0 or 1 row back. First* methods should be used in cases where you expect there can be multiple matches but only want the first match, and should always be used with an OrderBy*() method to ensure the results are predictable.
You can use Update by performing an Exists check query that doesn't load a tracked entity. Examples would be:
Category exists = Context.SSActivewear_Category
.AsNoTracking()
.Where(b => b.CategoryID == x.CategoryID)
.SingleOrDefault();
or better:
bool exists = Context.SSActivewear_Category
.Where(b => b.CategoryID == x.CategoryID)
.Any();
Then you could use Context.Update(x). AsNoTracking() tells EF to load an instance but not track it. This would really be a waste in this case as it's a round trip to the DB to return everything in the Category only to check if something was returned or not. The Any() call would be a round trip but just does an EXISTS db query to return true or false.
However, these are not fool-proof as there is no guarantee that the Context instance isn't already tracking an instance for that Category from some other operation. Something as trivial as having a Category appear in CustomeClass twice for any reason would be enough to trip the above examples up as once you call Context.Update(x) that instance is now tracked, so the loop iteration for the second instance would fail.
I am creating a WPF app and I have an existing DB that I would like to use and NOT recreate. I will if I have to, but I would rather not. The DB Is sqlite and when I add it to my data later and create a DataModel based on the DB, I get the model and the DB Context, however there are no methods created for CRUD or for instance .ToList() so I can return all of the items on the table.
Do I need to create all of these manually or is there a way to do it like the way that MVC can scaffold?
I am using VS 2017, WPF, EF6 and Sqlite installed with Nu-Get
To answer the question in the title.
No.
There is no click-a-button method of scaffolding out UI like you get with MVC.
If you just deal with a table at a time then you could build a generic repository that returns a List for a given table. That won't save you much coding, but you could do it.
If you made that return an iQueryable rather than just a List then you could "chain" such a query. Linq queries aren't turned into SQL until you force iteration and you can base one on another adding criteria, what to select etc etc for flexibility.
In the body of your post you ask about methods to read and write data. This seems to be almost totally unrelated from the other question because it's data access rather than UI.
"there are no methods created for CRUD or for instance .ToList() so I can return all of the items on the table."
There are methods available in the form of LINQ extension methods.
ToList() is one of these, except it is usual to use async await and ToListAsync.
Where and Select are other extension methods.
You would be writing any model layer that exposed the results of those though.
I'm not clear whether you are just unaware of linq or what, but here's an example query.
var customers = await (from c in db.Customers
orderby c.CustomerName
select c)
.Include(x => x.Orders) //.Include("Orders") alternate syntax
.ToListAsync();
EF uses "lazy loading" of related entities, that Include makes it read the Orders for each customer.
Entity Framework is an Object Relational Mapper
Which means it will Map your C# objects to Tables.
Whenever you are creating a model from bd it will create a Context Class which will in inherit the DbContext. in this class you will find all the tables in DbSet<Tablename> Tablename{get; set;}. Basically, this list contains will the rows. the operation performed on this list will affect the DB on SaveChange method.
Example for CURD
public DbSet<Student> Students { get; set; }
//Create
using (var context = new YourDataContext()) {
var std = new Student()
{
Name = "Aviansh"
};
context.Students.Add(std);
context.SaveChanges();
}//Basically saving it will add a row in student table with name field as avinash
//Delete
using (var context = new YourDataContext()) {
var CurrentStudent=context.Students.FirstOrDefault(x=>x.Name=="Avinash")
CurrentStudent.context.Students.Remove(CurrentStudent);
context.SaveChanges();
}
Note: on SaveChanges the change will reflect on Db
In my database, all my tables have 5 repeating fields (CreateUser, UpdateUser, IsRemoved...)
Now, I call the DbContext normally as:
mydbEntities db = new mydbEntities();
And then when I need to bring up all the students and all classes I call them normally as:
var students = db.students;
var classes = db.classes;
BUT! Since I have to NOT bring the ones that have column IsRemoved == true , I'm rewriting everywhere in my app to:
var students = db.students.Where(m => m.IsRemoved == false);
var classes = db.classes.Where(m => m.IsRemoved == false);
To avoid bugs and errors when replacing existing code, when the scaffolding creates new controllers, and other developers forgetting to add the Where(), makes me wonder if I can overwrite the call db.students so that internally, it ALWAYS adds the Where()
This will also help in the future, so when I call db.students.Include(s => s.classes) that both times the IsRemoved is taken into account and even if the student exists, if the class has been removed, then it won't return it in the list.
This is why I believe that having Repository classes - for the database access layer - is always a good idea. So you never have to access your dbContext object directly, but through a layer of abstraction, eg. the StudentRepositoy.
IStudentRepository studentRepository = new StudentRepository(dbContext);
var students = studentRepository.getStudents();
In this case, you only need to add the Where(m => m.IsRemoved == false) once, inside your getStudents() method.
I hope you don't have those queries repeated in your controllers (I don't know if you are using MVC or some other thing). Check out this tutorial for more detail information.
Although, this does not answer your question, I hope you consider these modifications.
Edit:
It does look like there is a way to override dbcontext from the documentation
Have you considered creating a view in the database that accounts for the IsRemoved flag?
Select * From <tablename> Where IsRemoved = 0
Then add the view to your DBContext.
Just an idea.
As a simplified example I have users, products and customers. Users are allowed access to certain products and to certain customers.
I'm using an edmx-file to map my SQL Server to my code and get the data using linq. A typical query might look something like this:
from prod in ctx.Products
join userProduct in ctx.UserProduct
on prod.Id equals userProduct.ProductId
join user in ctx.UserProfile
on userProduct.UserId equals user.Id
where user.UserName == username // <-- username is a method parameter
select new Product
{
Id = prod.Id,
DisplayText = prod.UserFriendlyText
}
Every time I need data from the database I must join towards the access rights table to EXCLUDE data the user does not have access to. This means that if someone (and it will happen eventually) forget to join towards the access table a user will see too much. Is there a way to INCLUDE data instead so that if I forget the access tables nothing is shown?
I've also been thinking about separating the different customers into different databases as their data will never be related to each other and it will be a small disaster if I leak data between customers. Leaking products between users from the same customer is bad but not as critical.
If it matters I'm in a C# MVC4 CQRS architecture with eventual consistency between the read and write side.
I've checked stack overflow for similar questions but all I could find was this unanswered one:
Access rules in CQRS read model
How about using the Repository pattern, and forcing your Dev's to use it to make calls to the Database? This will promote code reuse and improve the maintainability of the app.
Because a method will be called from the repository you can control the code that interacts with the database, and force consistency, that way you can make sure that the access table is always used, and used as you wish.
I have a similar problem in my database. 90% of my entities are "organisation dependent". My approach uses a generic base repository with methods like this:
public virtual T Find(int id)
{
T e = Context.Set<T>().Find(id);
var od = e as OrganisationDependent;
if (od != null && od.OrganisationID != CurrentOrganisationID)
return null;
if (e == null)
return null;
return e;
}
The "All" method was a particular issue. Solved by How to conditionally filter IQueryable
private static readonly PropertyInfo _OrganisationIDProperty = ReflectionAPI.GetProperty<OrganisationDependent, int>(o => o.OrganisationID);
private static Expression<Func<TOrg, bool>> FilterByOrganization<TOrg>(int organizationId)
{
//The FilterByOrganisation method uses the LINQ Expressions API to generate an expression that will filter on organisation id
//This avoids having to cast the set using .AsEnumerable().Cast<OrganisationDependent>().Where(x => x.OrganisationID == CurrentOrganisationID).AsQueryable().Cast<T>();
//https://stackoverflow.com/questions/20052827/how-to-conditionally-filter-iqueryable-by-type-using-generic-repository-pattern
var item = Expression.Parameter(typeof(TOrg), "item");
var propertyValue = Expression.Property(item, _OrganisationIDProperty);
var body = Expression.Equal(propertyValue, Expression.Constant(organizationId));
return Expression.Lambda<Func<TOrg, bool>>(body, item);
}
public virtual IQueryable<T> All
{
get
{
if (typeof(T).IsSubclassOf(typeof(OrganisationDependent)))
return Context.Set<T>().Where(FilterByOrganization<T>(CurrentOrganisationID));
return Context.Set<T>();
}
}
This closes off most of the places that a user could access someone else's data. But it doesn't filter navigational properties. So I have to add code to all navigation properties on non-organisation dependent entities to do that.
I don't want to separate my data into different database, but one day I will find out if it's feasible to create views filtered by organisation in different schemas - with the same name and structure as my tables, then switch schema according to user.....oh and I want to automatically create them for each new organisation and autmatically migrate them using code-first too....
And you could vote to Allow filtering for Include extension method here
If you're using a CQRS style architecture you can think about having one or more viewmodels per user that contains the products/customers that they have access to.
If you see yourself having to implement logic on the query side of CQRS that is a strong indication that you are doing something wrong.
I have a simple databasescheme: User, Account. User has 1-to-many relationship with Account.
I have generated a ado.net entity data model, and I can create users and accounts, and even link them together. In the database the account.user_id is correctly filled, so theoretically I should be able to acces User.Account.ToList() in C# through entity.
However, When I try to acces User.Account.ToList() I get zero results.
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 0...
When I add the following code before the previous code it suddenly gives me the correct count 2.
Account account1 = db.Account.First(A => A.id == 1);
Account account2 = db.Account.First(A => A.id == 2);
User user = db.User.First(U => U.id == 1);
List<Account> accounts = user.Account.ToList(); ##count = 2...??
What am I missing here??
You should use the ObjectQuery.Include method for this. Your method works also but results in an additional query.
In your example you would get
User user = db.User.Include("Account").First(u => u.id == 1);
You have to figure out whether the string "Account" is correct. Usually it should be prefixed with something like MyEntities. This depends on the namespace of your entities but with a little trial and error you should be able to figure this out.
Yes, that's a common problem when starting to use the Entity framework - neither parent nor child relationships are lazy loaded so you have to load them explicitly. If you are going to share the object context around between classes / methods you might want to make a check to see if the relationship is already loaded:
e.g.
if(!user.Account.IsLoaded)
user.Account.Load();
You can make this easier with a simple extension method:
public static class EntityExtensions
{
public static void EnsureLoaded(this RelatedEnd relatedEnd)
{
if (!relatedEnd.IsLoaded)
relatedEnd.Load();
}
}
using this makes your load call shorter again:
user.Account.EnsureLoaded();
And as it uses a RelatedEnd, which is common to parent and child relationships in the entity framework, you can use this for parent reference relationships too - e.g.
account.UserReference.EnsureLoaded();
As rwwilden says, if you are always going to load the child objects with the parent in this case, you might want to use an Include to make the call more efficient and avoid an extra roundtrip to the database.
I guess my knowledge is a bit small of the framework. :)
You need to explicitly load the related accounts first.
user.Account.Load();
Now It does display correctly.