简体   繁体   中英

Using Entity Framework Code-first am I required to have the model of my domain anemic?

with Entity Framework Code-First I have to have all properties public to generate database, this mean using Entity Framework Code-First approach I am forced to have an anemic domain mixed with a rich model? Because Properties that need not be public, have to be.

For example:

Using a rich model, this in Entity Framework will not work:

public class Car
{
    private int _actualPosition;

    public void Accelerate()
    {
        this._actualPosition += 10;
    }
}

To work we need to make _actualPosition public:

public class Car
{
    public int ActualPosition { get; set; }

    public void Accelerate()
    {
        this.ActualPosition += 10;
    }
}

Now Entity IMO is ugly, because I have a method that adds + 10 in the property and have it public at the same time, I don't want that property be public.

Another Example:

Imagine I want a relationship many-to-many but with only one way like:

 
 
 
 
  
  
  public class User { public long Id { get; set; } public string Name { get; set; } public IList<Role> Roles { get; set; } } public class Role { public long Id { get; set; } public string Name { get; set; } }
 
 
  

How I do a many-to-many relationship with that model? To my knowledge there's no way I'll have to do a two-way relationship:

 
 
 
 
  
  
  public class User { public long Id { get; set; } public string Name { get; set; } public IList<Role> Roles { get; set; } } public class Role { public long Id { get; set; } public string Name { get; set; } public IList<User> Users { get; set; } }
 
 
  

With this model I'll be able to make many-to-many relationship:

 
 
 
 
  
  
  modelBuilder.Entity<User>() .HasMany(x => x.Roles) .WithMany(y => y.Users);
 
 
  

I'm right? If yes, Entity Framework is not pleasing me.

The "Another Example" work with answer of @Slauma :

 modelBuilder.Entity<User>() .HasMany(x => x.Roles) .WithMany() .Map(a => { a.MapLeftKey("UserId"); a.MapRightKey("RoleId"); a.ToTable("UserRoles"); }); 

Your "Another example" is not correct. You are not forced to expose both ends of a relationship as properties in your model classes. In your example you can remove public IList<User> Users { get; set; } public IList<User> Users { get; set; } public IList<User> Users { get; set; } from your Role class and define the mapping like so:

modelBuilder.Entity<User>()
            .HasMany(x => x.Roles)
            .WithMany()
            .Map(a =>
            {
                a.MapLeftKey("UserId");
                a.MapRightKey("RoleId");
                a.ToTable("UserRoles");
            });

This is the case also for one-to-many relationships. There is always a parameterless With... overload, WithMany() , WithRequired() , WithOptional() , etc. to define mappings for relationships where not both ends are exposed in the model.

Short Answer: Unless the framework supports it, you are stuck trying to jam too much into this class if you attempt with both business rules AND public properties.

Long Answer: I once used a system very like this, and basically I came to the realization that the best way for testing and encapsulation was to wrap this class with my real domain class.

public class Car
{
    private CarPersistence _carPersistence;

    public void Accelerate()
    {
        this._carPersistence.ActualPosition += 10; 
    }

    public Car (CarPersistence car) {
        _carPersistence = car;
    }
}

public class CarPersistence
{
    public int ActualPosition;
}

When I did this before, I was able to then have my lower level Persistence class implement an interface that could be used by my tests to inject into the Business class for simple testing without having to talk to the database.

A system like this, with your business classes on one "layer" and your persistence classes on another "layer" you can pretty easily have one-to-many relationships, it just has to be built into whatever builds your business classes out of persistence classes:

public class User
{
   private UserPersistence _user;
   private IList<Persistence> _roles;

   public User (UserPersistence user, IList<Persistence> roles) {
       _user = user;
       _roles = roles;
   }
}

Sorry, the syntax might not be perfect, that's the best I can remember it transliterating from VB into C# ;)

You can setup your Car Entity like this:

public class Car
{
    public int ActualPosition { get; private set; }

    public void Accelerate()
    {
        this.ActualPosition += 10;
    }
}

This forces modification of the ActualPosition property to use the specialized Accelerate method. A side effect of using this style is that your Accelerate method is not testable.

You can also set it up like this:

public class Car
{
    protected int ActualPosition { get; set; }

    public void Accelerate()
    {
        this.ActualPosition += 10;
    }
}

However this approach isn't directly testable without using reflection.

I believe what you are looking for is the following:

 public class Car
{
    public int Id { get; set; }
    public int ActualPosition { get; private set; }

    public void Accelerate()
    {
        this.ActualPosition += 10;
    }
}

The unit test is below :

 [TestMethod]
    public void AccellerateWillAddTenUnitsToTheActualPosition()
    {
        //Arrange
        var car = new Car();
        var actualPosition = car.ActualPosition;
        var expectedPosition = actualPosition + 10;

        //Act
        car.Accelerate();

        //Assert
        Assert.AreEqual(car.ActualPosition, expectedPosition);
    }

Yes, the code that you posted is an Anemic Model . Furthermore, I would not even call it a proper object oriented code. There is simply no encapsulation and objects are just a data containers. Looks like your ORM of choice is limiting your domain implementation choices. For what it's worth, NHibernate allows you to map fields directly (even if these fields are readonly). It does not require objects to have properties. For example:

public class Car {

    private int _position;

    public void Accelerate() {
        _position += 10;
    }
}

Can be mapped as:

<class name="Car" table="Cars" >
    ...
    <property name="_position" column="Position" />
    ...
</class>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM