I was writing a web app and learning entity framework along the way. I am curios if I have done something wrong though. I haven't been using dispose or using statements when querying.
Example of a Repository of mine:
public User GetUserById(int sessionId)
{
var user = (from u in db.Users where u.Id == sessionId select u).SingleOrDefault();
return user;
}
My User table I selected from would have foreign keys which would be associated with other tables. The cool thing, or so I thought, about entity is that I could return the entire User object (built by entity) which then would allow me to do something like this User.MyOtherTable.SomeColumn in my controller.
The problem is I relied on this and went about my merry way in my controller of grabbing the user information as well as utilizing information from the other table. I am now realizing that if I close that connection like the code below:
public User GetUserById(int sessionId)
{ using(db)
{
var user = (from u in db.Users where u.Id == sessionId select u).SingleOrDefault();
return user;
}
}
My controller no long has access to User.MyOtherTable.SomeColumn as this will be null. My true question is how important is it for me use dispose in my entity application?
I would strongly recommend that you use using statements, and also that you stop relying on lazy-loading.
Instead of selecting User objects with lazy-load properties, work out the full data you need and project that to a Model class, e.g.
public class UserWithOtherStuffModel
{
public int Id { get; set; }
public string Name { get; set; }
public string OtherStuff { get; set; }
}
Then in your method:
public UserWithOtherStuffModel GetUserById(int sessionId)
{
using(db)
{
var user = (from u in db.Users
where u.Id == sessionId
select new UserWithOtherStuffModel{
Id = u.Id,
Name = u.Name,
OtherStuff = u.MyOtherTable.SomeColumn
}).SingleOrDefault();
return user;
}
}
Not only does this mean you limit the number of database calls, but you have a data contract that isn't tied to your database model. If you move that column, you simply update the query to point to the new spot/name, the view can remain unchanged.
So when you dispose your context, you aren't going to be able to reference properties that are lazy loaded. You'll need to make your context live longer either by having the context disposed when the controller is disposed (I don't remember if MVC does this per request or not) or by controlling the lifetime of the context using some attributes on the Action.
Some examples on how to resolve this can be found in this question, which is pretty much the same as what you are asking here.
Related
This is my example app scenario:
public class SampleDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=ExampleUserDB;Trusted_Connection=True;");
}
}
public class User
{
public int Id { get; set; }
public bool AnalyseInProgress { get; set; }
public bool Analysed { get; set; }
}
public class UserService
{
public User GetUser()
{
using (var context = new SampleDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
var entity = context.Users
.Where(x => !x.Analysed)
.Where(x => !x.AnalyseInProgress)
.FirstOrDefault();
entity.AnalyseInProgress = true;
context.SaveChanges();
return entity;
transaction.Commit();
}
}
}
public void MarkUserAsAnalysed(int id)
{
using (var context = new SampleDbContext())
{
var entity = context.Users
.Find(id);
entity.Analysed = true;
context.SaveChanges();
}
}
}
By adding transaction and AnalyseInProgress property, I wanted to prevent returning the same user to multiple applications, if they call UserService.GetUser at the same time.
Unfortunately, it doesn't work as I expected. Sometimes, service returns the same user. Is there any built-in solution to that?
Thanks.
To ensure that two connections across systems cannot end up assessing the same row as available, you need to employ pessimistic locking on the connection when reading. Note that pessimistic locking when used extensively will lead to performance issues, so your focus should be to only use it where absolutely necessary and for as little time as needed. Get in, get done, get out.
public User GetUser()
{
User user = null;
var txOptions = New TransactionOptions {IsolationLevel = System.Transactions.IsolationLevel.Serializable};
using(var txScope = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
using (var context = new SampleDbContext())
{
user = context.Users
.OrderBy(x => x.CreatedAt)
.FirstOrDefault(x => !x.Analysed && !x.AnalyseInProgress);
if(user != null)
{
user.AnalyseInProgress = true;
context.SaveChanges();
context.Entry(user).State = EntityState.Detached;
}
}
txScope.Complete();
}
return user;
}
So a couple things here. First we use an isolation level of Serializable to ensure that only one operation can read this user row if it is finalized. Multiple calls to GetUser, such as coming from multiple web servers will wait if one of the other running calls is still within that Tx Scope. When returning entities outside of the scope of the DbContext that read them then it is best to ensure that they are marked as Detached. Normally you would read these AsNoTracking but since we want to update that flag on them first, we read the entity as a tracked instance, perform the update, then detach it before returning. In your original code the transaction.Commit() would never run because it was after your return statement. (intellisense would be highlighting that.)
When using FirstOrDefault like that you should specify an OrderBy clause, and you need to handle the possibility that no record is returned. The original code would have thrown a NullReferenceException the minute all unprocessed users was achieved.
I don't recommend passing detached entities around, but rather DTOs or ViewModels to avoid confusion around whether a method accepting an entity is getting a tracked, real entity, or getting a detached, or worse, partially filled entity-like class.
I would also recommend considering using an enumeration or such to represent the User's "Status" or such rather than individual flags like "IsProcessing" "IsProcessed". For instance using an enumeration for "Unprocessed, Processing, Processed" means you can simplify the logic to:
user = context.Users
.OrderBy(x => x.CreatedAt)
.FirstOrDefault(x => x.Status == UserStatuses.Unprocessed);
then:
user.Status = UserStatuses.Processing;
It can save accidentally forgetting to append combinations of flags to Where type queries.
I've been trying to take advantage of a new way of creating many-to-many relationships - nice article about EF 5 many-to-many relationships.
The article states that you no longer need to define relation class and the framework does the job for you.
However, for a couple of hours now I've been struggling to add an existing entity to the collection of another entity.
My models
public record Bottle
{
[Key]
public int Id { get; set; }
[Required]
public string Username { get; set; }
// some other properties
public Collection<User> Owners { get; set; }
}
public record User
{
[Key]
public int Id { get; set; }
// some other properties
public Collection<Bottle> Bottles { get; set; }
}
Say that I want to add a new bottle to the database. I also know owners of that bottle. I had thought that this bit of code could work:
public async Task<int> AddBottle(BottleForAddition bottle)
{
var bottleEntity = mapper.Map<Bottle>(bottle);
bottleEntity.Owners = bottle
.OwnerIds // List<int>
.Select(id => new User { Id = id })
.ToCollection(); // my extension method
var createdEntity = await context.AddEntityAsync(bottleEntity);
await context.SaveChangesAsync();
return createdEntity.Entity.Id;
}
but sadly it does not work (BottleForAddition is DTO with almost the same properties).
I get this error:
Unable to create bottle (error: Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: Users.Username'.
at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
at ...
So I came up with this
public async Task<int> AddBottle(BottleForAddition bottle)
{
var bottleEntity = mapper.Map<Bottle>(bottle);
bottleEntity.Owners = (await context.Users
.Where(u => bottle.OwnerIds.Contains(u.Id))
.ToListAsync())
.ToCollection();
var createdEntity = await context.AddEntityAsync(bottleEntity);
await context.SaveChangesAsync();
return createdEntity.Entity.Id;
}
That works but I have to fetch Users from the database.
Do you know about a better way how to deal with it?
The Users table in the database has a Username field does not allow NULL
You are creating new User entities from the OwnerIds which doesn't have Username value set
EF is trying to insert a new user to the Users table
Combining the pieces of information above, you'll get a clear picture why the error message says -
SQLite Error 19: 'NOT NULL constraint failed: Users.Username'.
Then comes the real question, why EF is trying to insert new users at all. Obviously, you created the User entities from the OwnerIds to add already existing users to the list, not to insert them.
Well, I'm assuming that the AddEntityAsync() method you are using (I'm not familiar with it) is an extension method, and inside it, you are using the DbContext.Add() or DbSet<TEntity>.Add() method. Even if that is no the case, apparently AddEntityAsync() at least works similarly as them.
The Add() method causes the entity in question (Bottle) and all it's related entities (Users) present in the entity-graph to be marked as Added. An entity marked as Added implies - This is a new entity and it will get inserted on the next SaveChanges call. Therefore, with your first approach, EF tried to insert the User entities you created. See details - DbSet<TEntity>.Add()
In your second approach, you fetched the existing User entities first. When you fetch existing entities using the DbContext, EF marks them as Unchanged. An entity marked as Unchanged implies - This entity already exists in the database and it might get updated on the next SaveChanges call. Therefore, in this case the Add method caused only the Bottle entity to be marked as Added and EF didn't try to re-insert any User entities you fetched.
As a general solution, in a disconnected scenario, when creating new entity with an entity-graph (with one or more related entities) use the Attach method instead. The Attach method causes any entity to be marked as Added only if it doesn't have the primary-key value set. Otherwise, the entity is marked as Unchanged. See details - DbSet<TEntity>.Attach()
Following is an example -
var bottleEntity = mapper.Map<Bottle>(bottle);
bottleEntity.Owners = bottle
.OwnerIds // List<int>
.Select(id => new User { Id = id })
.ToCollection(); // my extension method
await context.Bottles.Attach(bottleEntity);
await context.SaveChangesAsync();
Not related to the issue :
Also, since you are already using AutoMapper, if you define your BottleForAddition DTO as something like -
public class BottleForAddition
{
public int Id { get; set; }
public string Username { get; set; }
// some other properties
public Collection<int> Owners { get; set; } // the list of owner Id
}
then you will be able to configure/define your maps like -
this.CreateMap<BottleForAddition, Bottle>();
this.CreateMap<int, User>()
.ForMember(d => d.Id, opt => opt.MapFrom(s => s));
which could simplify the operation code like -
var bottleEntity = mapper.Map<Bottle>(bottle);
await context.Bottles.Attach(bottleEntity);
await context.SaveChangesAsync();
Fetching the Users is generally the correct course of action. This allows you to make the associations but also helps validate that the reference IDs passed from the client are valid. Fetching entities by ID is generally quite fast, so I'd consider avoiding async/await for this operation. async is suited for large or high-frequency operations where server responsiveness could be "hung up". Using it everywhere just leads to slower operations overall.
EF will want to use proxies for navigation properties both for lazy loading (not to be relied on as a crutch, but useful to avoid errors as a worst-case) as well as for change tracking.
public record Bottle
{
[Key]
public int Id { get; set; }
[Required]
public string Username { get; set; }
// some other properties
public virtual ICollection<User> Owners { get; set; } = new List<User>();
}
then in the applicable code...
var bottleEntity = mapper.Map<Bottle>(bottle);
var users = context.Users
.Where(u => bottle.OwnerIds.Contains(u.Id))
.ToList();
foreach(var user in users)
bottleEntity.Users.Add(user);
// Or since dealing with a new Entity could do this...
//((List<User>)bottleEntity.Users).AddRange(users);
await context.SaveChangesAsync();
return bottleEntity.Id;
It might be tempting to just create the users and attach them to the DbContext and much of the time this would work, except if there is ever the possibility that the DbContext might have been tracking an instance of any of those to-be-attached users, which will result in a runtime error that an entity with the same ID is already being tracked.
var bottleEntity = mapper.Map<Bottle>(bottle);
var proxyUsers = bottle.OwnerIds
.Select(x => new User { Id = x }).ToList();
foreach(var user in proxyUsers)
{
context.Users.Attach(user);
bottleEntity.Users.Add(user);
}
await context.SaveChangesAsync();
return bottleEntity.Id;
This requires either turning off all entity tracking or remember to always query entities with AsNoTracking which can lead to additional work and intermitted bugs appearing if this isn't adhered to consistently. To deal with possible tracked entities is a fair bit more work:
var bottleEntity = mapper.Map<Bottle>(bottle);
var proxyUsers = bottle.OwnerIds
.Select(x => new User { Id = x }).ToList();
var existingUsers = context.Users.Local
.Where(x => bottle.OwnerIds.Contains(x.Id)).ToList();
var neededProxyUsers = proxyUsers.Except(existingUsers, new UserIdComparer()).ToList();
foreach(var user in neededProxyUsers)
context.Users.Attach(user);
var users = neededProxyUsers.Union(existingUsers).ToList();
foreach(var user in users)
bottleEntity.Users.Add(user);
await context.SaveChangesAsync();
return bottleEntity.Id;
Any existing tracked entity needs to be found and referenced in place of an attached user reference. The other caveat of this approach is that the "proxy" users created for non-tracked entities are not complete user records so later code expecting to get User records from the DbContext could receive these attached proxy rows and result in things like null reference exceptions etc. for fields that were not populated.
Hence, fetching the references from the EF DbContext to get the relatable entities is generally the best/simplest option.
I've read some tutorials with entity framework 6...
The basics are easy.
using (var context = new MyContext())
{
User u = context.Users.Find(1);
}
But how to use "Where" or something else on the "DbSet" with the users?
public class MyContext : DbContext
{
public MyContext()
: base("name=MyContext")
{
//this.Database.Log = Console.Write;
}
public virtual DbSet<User> Users { get; set; }
}
Users
[Table("User")]
public class User : Base
{
public Guid Id { get; set; }
[StringLength(100)]
public string Username { get; set; }
}
And thats the problem which doesnt work.
string username = "Test";
using (var context = new MyContext())
{
User u = from user in context.Users where user.Username == username select user;
}
Error: There was no implementation of the query pattern for source type 'DbSet'. 'Where' is not found. Maybe a reference or a using directive for 'System.Link' is missing.
If i try to autocomplete the methods there are none.
Why it doesnt works? :(
// Edit:
Adding System.Linq to the top of the file changes the functions of the problem above so that i havent a problem anymore.
But why the where is wrong now?
The type "System.Linq.IQueryable<User>" cant converted into "User" explicit. There already exists an explicit conversion. (Possibly a cast is missing)
Thanks to #Grant Winney and #Joe.
Adding using System.Linq; to the namespace/top of the document where i'm tring the code above fixed the problem.
And using the line above it works for the first item of the list.
User user = (select user from context.Users where user.Username == username select user).First();
The (second) problem is in what you expect:
User u = from user in context.Users where user.Username == username select user;
You're expecting a single element. But a Where clause returns a list (IEnumerable) of items. Even if there's only one entity that fits the where clause, it will still return a list (with a single item in it).
If you want a single item, you need to either take the .First() or the .Single() item of that list.
Some considerations:
Either method I just mentioned can take a clause similar to how the where clause works. This means you can skip the Where and put the clause straight in this method.
Single only works if only one element exists (fitting the clause). If two elements occur, an exception will be thrown.
First works similar to Single, but it will not throw an exception if multiple items exist (fitting the clause). It will simply return the first element it finds (fitting the clause).
I've got two entity classes User and Connection, and User has a collection of Connection.
class User
{
public string username {get; set;}
public ICollection<Connection> Connections {get; set;}
}
class Connection
{
public string ConnectionId {get; set;}
public string RoomName {get; set;}
}
I've got this SQL Query which retrieves a list of connections for a user
// this code can be moved to the database as a stored procedure or view
var sql = #"SELECT Users.UserName, Users.UserPix,
Connections.ConnectionId, Connections.RoomName, Connections.DateCreated
FROM Users CROSS JOIN Connections
WHERE (Users.UserName = #p0) AND (Connections.RoomName = #p1)";
return _context.Users.SqlQuery(sql, username, roomName).FirstOrDefault();
It returns a user object with an empty connection list, rather than populating the connections with data returned from the database.
I've tried replacing cross-join with inner-join but still the same result
I don't know how else to modify the sql query so that the actual data is returned. How can I do this or is there something I'm missing?
Since you aren't doing anything special here (there aren't even any projections) you can just return the entities directly from the context, including the navigation properties you want with them:
e.g. given these entities:
class User
{
public string username { get; set; }
public ICollection<Connection> Connections { get; set; }
}
class Connection
{
public string ConnectionId { get; set; }
public string RoomName { get; set; }
}
A one-to-many relationship should exist in your database between User -> Connection
In order to get all connections for a particular user, or all connections for all users, or any combination of queries you can think of with filters/aggregates etc on users/connections whilst preserving the relationship you can just use .Include() from QueryableExtensions in System.Data.Entity
Signature is as so if you are interested:
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class
Which is a good thing about EF, you can eager load the child entities on demand without having to mess with the entity design or add new entity classes which don't contain the navigation properties
So basically it boils down to this:
using(YourDbContext context = new YourDbContext())
{
var query = context.Users.Include(user => user.Connections);
// Do stuff with query
}
Don't forget about deferred execution - if you don't know what deferred execution is, and how a query is built up in EF it's worth looking it up - the general rule of thumb is; the less queries that hit the database and the less crunching you do in C# (for database style operations like aggregates) the quicker your app is going to perform - so make sure you don't enumerate the results until you've fully built the query or you are going to be doing all the number crunching on the .NET side using LINQ to objects, not in SQL where it should be!
So calling .ToList() or enumerating the above query will get you the results
EF will translate anything you have done to the correct SQL dialect using the provider you have chosen (likely System.Data.SqlClient). You don't need to write any SQL...
Some other examples of queries you might do:
// Get me users called Fred including their connection details
context.Users.Include(x => x.Connections).Where(u => u.Username == "Fred")
// Get me users that are currently connected to "Game Room"
context.Users.Include(x => x.Connections).Where(u => u.Connections.Any(c => c.RoomName == "Game Room")
None of this requires you to write any SQL - put a SQL trace on when running these queries to see what EF does, and usually it will write queries better than you ever could :) (only sometimes it doesn't and it's usually when you are doing something silly)
Edit
Ok I see you are trying to filter both the user and the connections that come back over the wire, in that case you either need to explicitly load the navigation property as a separate query or use projection to do the filtering
e.g.
Explicit Loading
var user = context.Users.First(x => x.UserName == username);
context.Entry(user).Collection(x => x.Connections).Query().Where(x => x.RoomName == roomName).Load();
That does result in two queries
Projection
var usersConnections = context.Users
.Where(u => u.UserName == userName)
.Select(u => new
{
User = u,
Connections = u.Connections.Where(c => c.RoomName == roomName)
});
This results in an anonymous type which contains a User property and a Connections property. You can always project into a known type too if you need to send this over some sort of domain boundary
This will be a single query against the datasource
To do this I first retrieve/check if the user exist, then I retrieve their connections and add it to the list of connection for that user
public User GetUserAndConnections(string username, string roomname)
{
var user = _context.Users.Find(username);
if (user != null)
{
var connections =
_context.Users.Where(u => u.UserName == username)
.SelectMany(x => x.Connections.Where(p => p.RoomName == roomName))
.ToList();
user.AddExistingConnections(connections);
}
return user;
}
This is my first experience with EF so I'm probably doing something stupid. Any comments on the architecture are welcome.
So I have the typical class of Users. Users have a username and a list of roles:
public class User
{
public string UserID{ get; set; }
public List<Role> Roles { get; set; }
public int Id { get; set; }
public User()
{
Roles = new List<Role>();
}
}
My domain objects live in their own code library along with the interfaces for their repositories. So in this case there would be an IUserRepository with all the CRUD methods plus any specialized data access methods I might need. What I'm trying to do is implement these repository interfaces with EF4 in another class library. Any problems with this design so far?
Now in the db (sql server) I have the typical tables: Users, Roles, and a many-to-many table mapping users to roles UsersRoles.
I have successfully set up most of the CRUD methods in the EF lib. Here is what Save looks like
public void Save(Drc.Domain.Entities.Staff.User member)
{
using (var ctx = new DrcDataContext())
{
var efUser = MapFromDomainObject(member);
if(member.Id < 1)
{
ctx.Users.AddObject(efUser);
}
else
{
ctx.Users.Attach(efUser);
ctx.ObjectStateManager.ChangeObjectState(efUser, EntityState.Modified);
}
ctx.SaveChanges();
member.Id = efUser.UserId;
}
}
Now I'm not sure if this is the proper way of accomplishing this but it works. However, I run into problems when doing a delete. The problem is with the related tables
public void Delete(Drc.Domain.Entities.Staff.User member)
{
using (var ctx = new DrcDataContext())
{
var efUser = MapFromDomainObject(member); ctx.Users.Attach(efUser);
while (efUser.Roles.Count > 0)
{
ctx.ObjectStateManager.ChangeObjectState(efUser.Roles.First(), EntityState.Deleted);
}
ctx.SaveChanges();
ctx.ObjectStateManager.ChangeObjectState(efUser, EntityState.Deleted);
ctx.SaveChanges();
}
}
If I don't delete the roles in the while loop I get a DELETE conflict with reference constraint error. If I run the code above it does delete the proper rows in the many-to-many table but it also deletes rows in the Roles table. I'm at a dead end now and considering scraping the ORM idea and writing my repository implementations in good ole reliable ADO.net.
--Edit I'm guessing that this is not the correct way to implement repositories with EF. Is it possible to do without littering your domain with a bunch of EF-centric stuff?
Use simply the standard approach and don't mess around with the entity state:
public void Delete(Drc.Domain.Entities.Staff.User member)
{
using (var ctx = new DrcDataContext())
{
var efUser = MapFromDomainObject(member);
ctx.Users.Attach(efUser);
ctx.Users.DeleteObject(efUser);
ctx.SaveChanges();
}
}
There is usually a cascading delete in the database from the User table to the join table (if you didn't disable it by hand). So deleting the user will delete the corresponding rows in the join table as well (but not the roles of course).
Setting the state of an entity to Deleted is not the same as calling DeleteObject. It will only set the parent to deleted and leave the children in an undeleted state in the context, leading to the constraint violation exception. DeleteObject will also mark the children in the context as Deleted and therefore avoid the exception.