skip to Main Content

I wonder how to initialize a property like this in EntityFramework:

public class Activity : Model
{
    public User User { get; set; }
}

User looks like:

public class User : Model
{
    public string Email { get; set; }

    public string Password { get; set; }

    public string Name { get; set; }
}

Thank you!

2

Answers


  1. If you want to establish a one-to-one relationship; You can make the following definition.

    public class Activity : Model
    {
        public int UserId { get; set; }
        public User User { get; set; }
    }
    
    
    public class User : Model
    {
        public string Email { get; set; }
    
        public string Password { get; set; }
    
        public string Name { get; set; }
    
        public int ActivityId { get; set; }
    
        public Activity Activity { get; set; }
    }
    

    Or, if you want to establish a One-to-many relationship; You can make the following definition.

    public class Activity : Model
    {
        public int UserId { get; set; }
    
        public User User { get; set; }
    }
    
    
    public class User : Model
    {
        public string Email { get; set; }
    
        public string Password { get; set; }
    
        public string Name { get; set; }
    
        public ICollection<Activity> Activities { get; set; }
    }
    

    You can choose the type of relationship that fits the data model.
    I also share the example context definition:

    public class SampleContext : DbContext
    {
        public DbSet<User> Users { get; set; }
    
        public DbSet<Activity> Activities { get; set; }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder )
        {
            //Example is for SqlServer.
            optionsBuilder.UseSqlServer("ConnectionString here");
        }
    }
    

    I also share how the migration is done.

    https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli

    Login or Signup to reply.
  2. From the question and comments it sounds like you want a proper OO approach to initializing a new Activity with a User where you would have a constructor like:

    public class Activity : Model
    {
        // Activity members...
    
        public User User { get; set; } 
    
        public Activity(int id, /* required fields */, User user) : base (id)
        {
            // set required fields.
            User = user;
        }
    }
    

    That alone won’t really work with EF because behind the scenes EF will want to construct entity instances and proxies and it won’t necessarily always want to load a User and any other related class every time it reads an Activity.

    The simple use case for entity classes is that when constructing an activity is to just use public setters with a default public constructor. This involves resolving any existing references like the user from the DbContext you will be using to add your new Activity:

    var user = _context.Users.Single(x => x.Id == userId);
    var newActivity = new Activity
    {
        //Activity fields...
        User = user
    };
    _context.Activities.Add(newActivity);
    

    A common problem in web applications is sending entities to a view then deserializing view data back into entities and expecting to associate those to a new entity being created. For example a POST controller method:

    [HttpPost]
    public ActionResult Create(/*required fields*/, User user)
    {
        var newActivity = new Activity
        {
            //Activity fields...
            User = user
        };
        _context.Activities.Add(newActivity);
        // ...
    }
    

    This will not work the way you expect because the User that gets passed in is not known by the DbContext instance associated to the Request. _context will treat that User instance as a new entity and try to add it to the database when it persists the new Activity. This will invariably lead to errors and/or unintended behaviour. While you will see many examples online where Entities are passed too and from views I highly recommend not falling into this trap and either be accustomed to passing simple view models, or just passing the necessary fields. In the above example we should not attempt to pass an entire User entity, we don’t need it and cannot reliably use it. All we need is the User Id:

    [HttpPost]
    public ActionResult Create(/*required fields*/, int userId)
    {
        var user = _context.Users.Single(x => x.Id == userId);
        var newActivity = new Activity
        {
            //Activity fields...
            User = user
        };
        _context.Activities.Add(newActivity);
        // ...
    }
    

    The typical argument against an approach like this is trying to avoid going to the DB to fetch a user. However, the counter-argument is that this serves to ensure that the provided references are still valid. In cases where we have many associations to do and don’t necessarily want to go to the database and we can reasonably trust the data coming in. (such as an internal line-of-business system used by employees, not the public) then there are shortcuts available:

    [HttpPost]
    public ActionResult Create(/*required fields*/, int userId)
    {
        var user = _context.Users.Local.FirstOrDefault(x => x.Id == userId);
        if (user == null)
        {
            user = new User { Id = userId };
            _context.Attach(user);
        }
        var newActivity = new Activity
        {
            //Activity fields...
            User = user
        };
        _context.Activities.Add(newActivity);
        // ...
    }
    

    Here we check the DbContext’s local cache for the given user. This doesn’t go to the database but just checks if the DbContext happens to be tracking an instance. In this simple example this step can be skipped since 100% the DbContext won’t be tracking anything, it is a fresh context. Where you would want to do this is in cases where this is in a method that might get called for several activities, or cases where you are dealing with the possibility of several references to the same record. (I.e. a collection of children entities that might each reference the same User ID) I included this step because it trips people up when they get used to just creating stubs or attaching entities sent in and have it work just fine, but then get a seemingly random runtime error when they hit a situation where the DbContext happens to already be tracking an instance for that ID. As a general rule before ever attaching an entity you should check for an existing tracked reference in the local cache.

    Where there isn’t a tracked reference, we create a stub, attach it to the DbContext so it treats it as an existing row, then associate the stub to the new Activity and Save. This saves time of loading references from the database but there is a big caveat to consider here. The data for the User and it’s associated Activity is not complete. This type of optimization is fine if we just want to get an activity into the database and leave it at that. We don’t want to do anything else with the activity. We don’t want to send it back to a view, or pass it to another method that expects a complete Activity and associated User. Accessing something like "newActivity.User.Name" for instance would return #null or cause exceptions as the "newActivity.User" is a reference to our stub User. Even if you try to fetch the user from the DbContext later, you will be given that Stub: For instance if you do something like this:

        user = new User { Id = userId };
        _context.Attach(user);
        var newActivity = new Activity
        {
            //Activity fields...
            User = user
        };
        _context.Activities.Add(newActivity);
    
        // somewhere later in the request...
        var aUser = _context.Users.Single(x => x.Id == userId);
    

    The result of that request to get that same user "aUser" from the DbContext will return you that stub reference, even though that statement will execute a query to the database. A final safety step whenever you attach a stub entity like this is to ensure you detach it after saving. This means keeping track of whether you used a stub or not:

    [HttpPost]
    public ActionResult Create(/*required fields*/, int userId)
    {
        var user = _context.Users.Local.FirstOrDefault(x => x.Id == userId);
        bool useUserStub = user == null;
        if (useUserStub)
        {
            user = new User { Id = userId };
            _context.Attach(user);
        }
        var newActivity = new Activity
        {
            //Activity fields...
            User = user
        };
        _context.Activities.Add(newActivity);
        if (useUserStub)
            _context.Entry(user).State = EntityState.Detached;
        // ...
    }
    

    Needless to say this starts getting complicated as you add more references with stubs. The simplest thing is to just fetch referenced entities from the database as fetching entities by ID is about as fast an operation as there is, and it provides meaningful validation where something isn’t found at the point it is requested rather than a FK constraint error at SaveChanges().

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search