Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework : Sharing entities across different DbContexts

I'm developing a plugin application with EF6, code first.

I have one main context with an entity called User:

public class MainDataContext : DbContext
{
    public MainDataContext(): base("MainDataContextCS") {}
    public DbSet<User> Users { get; set; }
}

And then another context for PluginX, on another project which references the base one:

public class PluginDataContext : DbContext
{
    public PluginDataContext () : base("MainDataContextCS") {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        modelBuilder.HasDefaultSchema("PluginX");
        base.OnModelCreating(modelBuilder);
    }

    public DbSet<Booking> Bookings { get; set; }
}

And this neatly creates, on the same Database (same connection string), the PluginX.Bookings Table.

The problem here is that the Booking entity contains a reference to User entity:

public class Booking
{
    public int Id { get; set;}
    public virtual User CreationUser { get; set;}
    public BookingStatus Status { get; set; }
}

And when running Add-Migration for the plugin context EF will try to create another User entity called PluginX.User.

How can this be solved? Is there a way to share a common entity, in another DbContext?

like image 701
tggm Avatar asked Jan 15 '15 17:01

tggm


People also ask

Should DbContext be singleton or transient?

DbContext should not be used as a singleton because it is holding a connection object which cannot be used by multiple threads at the same time.

Should DbContext be scoped or transient?

But if you use other injected services, with "transient" on the DBContext, every service gets his own instance of it. In order to avoid that you should always use "scoped" on the DBContext. Correct. Maybe there are cases in which you need a transient EF-Context - but usually you should stick to scoped.

Why is DbContext not thread-safe?

This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. When concurrent access goes undetected, it can result in undefined behavior, application crashes and data corruption.

Does DbContext need to be disposed?

Don't dispose DbContext objects. Although the DbContext implements IDisposable , you shouldn't manually dispose it, nor should you wrap it in a using statement. DbContext manages its own lifetime; when your data access request is completed, DbContext will automatically close the database connection for you.


4 Answers

When you work with multiple contexts you have two options:

  1. Treat each context like they were separate applications. Imagine your user is an external resource that you get from a web service. You won't be able to add a foreign key to that. What you would do in this is either add only the userId in your tables and when you need the user details call the external service to get them or have a local light copy of the user in the Bookings context that you would update every now and then from the Users context. This approach is good when you work with a large system and you want to isolate the parts (read about DDD and bounded contexts)
  2. Apart from your 2 contexts, create a third context with the whole model (users, bookings, etc). You will use the complete context to create the migrations and maintain the DB structure, but in the application you will use the smaller contexts. This is a very simple solution. It's easy to maintain the migrations with a single context and it still allows you to isolate the DB operation in smaller contexts that don't have access to unrelated entities.
like image 130
Francesc Castells Avatar answered Oct 03 '22 21:10

Francesc Castells


When you add the Booking entity, don't use the DbSet.Add() method. Instead use the DbSet.Attach() method and set the DbContext.Entry(Entity).State property for the Booking to EntityState.Added and make sure the DbContext.Entry(Entity).State for User stays EntityState.Unchanged.

So for example instead of doing this:

pluginDataContext.dbBooking.Add(myNewBooking);

Do this:

pluginDataContext.dbBooking.Attach(myNewBooking);
pluginDataContext.Entry(myNewBooking).State = EntityState.Added;

This is because the Add() method marks all entities in the object graph as EntityState.Added which will cause inserts without checking if the entity already exists in the database. The Attach() method simply makes the context begin tracking the entity.

This is why I almost never use DbSet.Add().

like image 25
JC Ford Avatar answered Oct 03 '22 23:10

JC Ford


You can try using views, declare the user as a view in PluginDataContext and when you perform the migration, type the method "create User view as ...", this allows you to relate the book to the user.

like image 45
Juan Jimenez Cervantes Frigols Avatar answered Oct 03 '22 22:10

Juan Jimenez Cervantes Frigols


This solution could help you:Entity Framework 6 Code First Migrations with Multiple Data Contexts. However, in this case, both context are in the same project. I don't know if works with contexts that are in two different projects (I think it should if you are using the same class to map User). As the blog said, you need to comment the generated code related to the Users table when you run the Add-Migration command for the PluginX Context.

like image 21
octavioccl Avatar answered Oct 03 '22 23:10

octavioccl