简体   繁体   中英

How to abstract Entity Framework model properties

I have a model that looks like this :

public class Task : ITask
{
    public int DocumentId { get; set; }
    public virtual Document Document { get; set; }
    public TaskType TaskType { get; }
    public string Value { get; }
}

Now, this class is directly registered as a DbSet in the DbContext . This means that the Document property must be of concrete type. I want to make this code easily testable, so I want to have the property as an interface which is required by the ITask interface. What is the general way to approach this problem?

One way that comes to my mind is to put all such classes in a separate assembly but that seems a bit off.

Edit: The ITask interface is defined in a different assembly so it should not know about the Document type.

I would use EF models only for the data access layer and create a separate model for the business layer. The data access layer would be responsible for mapping the EF model to the business layer model and hand it to the business layer.

The business layer model can then also be immutable, which can have advantages. Also you can require all the properties to be eg not-null in you constructor and you can then rely on this throughout the whole business layer.

Of course you could argue that it's almost twice as much code to write. That's true, but IMO it results in cleaner code and therefore this is my preferred approach.

Interfaces can have properties defined in them, So your ITask can specify the document, like this:

public interface ITask {
    Document Document { get; set; }
}

But you also say that you want the Document property as an interface and this becomes tricky as you need a concrete type in the Task class. Generic interfaces will help here.

// The interfaces
public interface ITask<TDocument> where TDocument : IDocument, new() {
     TDocument Document { get; set; }
}

public interface IDocument {
    int Number { get; set; } // Example property
}


//The classes
public class Document : IDocument{
    public int Number { get; set; } // Example property
}

public class Task : ITask<Document> {
    public Document Document { get; set; }
}


// See if it works
public class Test {
    private Task myTask = new Task();

    public void TestMethod() {
        myTask.Document.Number = 1;
    }
}

Remember, use the concrete types in DBContext.

As to where the interfaces should be located, same assembly or their own, there's quite a few viewpoints on that. Personally, I put them in their own assembly away from the implementing classes. This question is worth a read: Should I have a separate assembly for interfaces?

One more comment, the class name Task is used in the .Net threading library, so maybe worth thinking about changing it to avoid potential confusion.

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