I have an entity class Person and its corresponding DTO class PersonDto.
public class Person: Entity
{
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual Sex Sex { get; set; }
public virtual Position Position { get; set; }
public virtual Division Division { get; set; }
public virtual Organization Organization { get; set; }
}
public class PersonDto: Dto
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public Guid SexId { get; set; }
public Guid PositionId { get; set; }
public Guid DivisionId { get; set; }
public Guid OrganizationId { get; set; }
}
After receiving a DTO object I have to convert it into a person entity. Now I do it completely manually. The code looks like this.
public class PersonEntityMapper: IEntityMapper<Person, PersonDto>
{
private IRepository<Person> _personRepository;
private IRepository<Sex> _sexRepository;
private IRepository<Position> _positionRepository;
private IRepository<Division> _divisionRepository;
private IRepository<Organization> _organizationRepository;
public PersonEntityMapper(IRepository<Person> personRepository,
IRepository<Sex> sexRepository,
IRepository<Position> positionRepository,
IRepository<Division> divisionRepository,
IRepository<Organization> organizationRepository)
{
... // Assigning repositories
}
Person Map(PersonDto dto)
{
Person person = CreateOrLoadPerson(dto);
person.Name = dto.Name;
person.Phone = dto.Phone;
person.Email = dto.Email;
person.Sex = _sexRepository.LoadById(dto.SexId);
person.Position = _positionRepository.LoadById(dto.PositionId);
person.Division = _divisionRepository.LoadById(dto.DivisionId);
person.Organization = _organizationRepository.LoadById(dto.OrganizationId);
return person;
}
}
The code is in fact trivial. But as the number of entities grows so does the number of mapper classes. The result is lots of similar code. Another issue is that when there are mode associations I have to add constructor parameteres for additional repositories. I tried to inject a some kind of a repository factory instead, but it smelled a bad-known Service Locator so I reverted to an original solution.
Unit testing of these mappers also results in a number of similar-looking test methods.
With all this been said I wonder if there exists a solution that can reduce the amount of manually written code and make the unit testing easier.
Thanks in advance.
UPDATE
I'd accomplished the task with Value Injecter but then I realized that I could safely remove it and the rest would still work. Here is the resulting solution.
public abstract class BaseEntityMapper<TEntity, TDto> : IEntityMapper<TEntity, TDto>
where TEntity : Entity, new()
where TDto : BaseDto
{
private readonly IRepositoryFactory _repositoryFactory;
protected BaseEntityMapper(IRepositoryFactory repositoryFactory)
{
_repositoryFactory = repositoryFactory;
}
public TEntity Map(TDto dto)
{
TEntity entity = CreateOrLoadEntity(dto.State, dto.Id);
MapPrimitiveProperties(entity, dto);
MapNonPrimitiveProperties(entity, dto);
return entity;
}
protected abstract void MapNonPrimitiveProperties(TEntity entity, TDto dto);
protected void MapPrimitiveProperties<TTarget, TSource>(TTarget target, TSource source, string prefix = "")
{
var targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
var sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
foreach (var targetProperty in targetProperties) {
foreach (var sourceProperty in sourceProperties) {
if (sourceProperty.Name != string.Format("{0}{1}", prefix, targetProperty.Name)) continue;
targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
break;
}
}
}
protected void MapAssociation<TTarget, T>(TTarget target, Expression<Func<T>> expression, Guid id) where T : Entity
{
var repository = _repositoryFactory.Create<T>();
var propertyInfo = (PropertyInfo)((MemberExpression)expression.Body).Member;
propertyInfo.SetValue(target, repository.LoadById(id), null);
}
private TEntity CreateOrLoadEntity(DtoState dtoState, Guid entityId)
{
if (dtoState == DtoState.Created) return new TEntity();
if (dtoState == DtoState.Updated) {
return _repositoryFactory.Create<TEntity>().LoadById(entityId);
}
throw new BusinessException("Unknown DTO state");
}
}
Mapping of each entity is performed with a concrete class derived from BaseEntityMapper. The one for Person entities looks like this.
public class PersonEntityMapper: BaseEntityMapper<Person, PersonDto>
{
public PersonEntityMapper(IRepositoryFactory repositoryFactory) : base(repositoryFactory) {}
protected override void MapNonPrimitiveProperties(Person entity, PersonDto dto)
{
MapAssociation(entity, () => entity.Sex, dto.SexId);
MapAssociation(entity, () => entity.Position, dto.PositionId);
MapAssociation(entity, () => entity.Organization, dto.OrganizationId);
MapAssociation(entity, () => entity.Division, dto.DivisionId);
}
}
Explicitly calling MapAssociation protects against future properties renamings.
You can have a look on the two most used Object-Object mapper:
AutoMapper
AutoMapper is a simple little library built to solve a deceptively
complex problem - getting rid of code that mapped one object to
another. This type of code is rather dreary and boring to write, so
why not invent a tool to do it for us?
Value Injecter
ValueInjecter lets you define your own convention-based matching
algorithms (ValueInjections) in order to match up (inject) source
values to destination values.
There is a comparison article on SO: AutoMapper vs ValueInjecter
You can use GeDA for mapping any entity to a DTO object, it comes with either annotations or DSL support.
http://inspire-software.com/confluence/display/GeDA/FAQ
There are only basic examples on the wiki but jUnits of source code are full of useful examples
You can get it from sourceforge or google code manually or via maven dependency
Details are here: http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler
Related
When I save the change everything looks good. CaseWorkNote entity is properly created and added to workNotes collection (property of Case entity).
When CurrentUnitOfWork calls DbContext->SaveChanges() I see that my entity is there with status Added.
In the end nothing is saved to DB.
What I miss in my code or what I'm doing wrong?
Below is my code and screenshot with tracked entity.
Model:
public class Case : FullAuditedAggregateRoot<Guid>
{
[Required]
public CaseType Type { get; set; }
[Required]
public string Subject { get; set; }
public string Descripion { get; set; }
//Aggregated entity
private HashSet<CaseWorkNote> _workNotes;
public IEnumerable<CaseWorkNote> WorkNotes => _workNotes?.ToList();
//
public CaseWorkNote AddNote(string text)
{
if (_workNotes is null)
{
_workNotes = new HashSet<CaseWorkNote>();
}
CaseWorkNote workNote = CaseWorkNote.Create(this, text);
_workNotes.Add(workNote);
return workNote;
}
}
public class CaseWorkNote : FullAuditedEntity
{
[ForeignKey("CaseId")]
[Required]
public Case Case { get; private set; }
[Required]
public string Text { get; set; }
private CaseWorkNote() : base() { }
public static CaseWorkNote Create(Case kase, string text)
{
return new CaseWorkNote()
{
Case = kase,
Text = text
};
}
}
DBcontext:
public class testDbContext : AbpZeroDbContext<Tenant, Role, User, testDbContext>
{
public DbSet<Case> Cases { get; set; }
public DbSet<CaseWorkNote> CaseWorkNotes { get; set; }
public testDbContext(DbContextOptions<testDbContext> options)
: base(options) { }
public override int SaveChanges()
{
//Here I see CaseWorkNote entity with state = "Added"
var entries = this.ChangeTracker.Entries();
foreach (var item in entries)
{
Debug.WriteLine("State: {0}, Type: {1}", item.State.ToString(), item.Entity.GetType().FullName);
}
return base.SaveChanges();
}
}
Application Service Class:
public class CaseAppService : AsyncCrudAppService<Case, CaseDto, Guid, PagedCaseResultRequestDto, CreateCaseDto, UpdateCaseDto>, ICaseAppService
{
//Removed for brevity
...
//
public async Task AddWorkNote(CreateUpdateCaseWorkNoteDto input)
{
var kase = await this.GetEntityByIdAsync(input.CaseId);
kase.AddNote(input.Text);
CurrentUnitOfWork.SaveChanges();
}
protected override async Task<Case> GetEntityByIdAsync(Guid id)
{
var kase = await Repository
.GetAllIncluding(c => c.WorkNotes)
.FirstOrDefaultAsync(c => c.Id == id);
if (kase == null)
{
throw new EntityNotFoundException(typeof(Case), id);
}
return kase;
}
public async Task<ListResultDto<CaseWorkNoteDto>> GetWorkNotes(EntityDto<Guid> entity)
{
var kase = await this.GetEntityByIdAsync(entity.Id);
return new ListResultDto<CaseWorkNoteDto>(MapToEntityDto(kase).WorkNotes.ToList());
}
}
thanks
The problem is caused by the default EF Core property access mode and ToList() call here
public IEnumerable<CaseWorkNote> WorkNotes => _workNotes?.ToList();
Not sure what type of methodology are you following, but you are violating the simple good design rule that property (and especially collection type) should not allocate on each get. Not only because it is inefficient, but also allows the "smart" client like EF Core to detect the actual type as List and try using it to add items when loading related data.
In reality with this type of implementation they are adding to a list which is discarded, in other words - nowhere. So EF Core loading related data / navigation property fixup doesn't work, which also may affect the change tracker and lead to weird behaviors.
To fix the EF Core issue, you should configure EF Core to use directly the backing field. The easiest way is to set it globally inside the OnModelCreating override:
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);
It also can be set per entity or per entity property, but I would suggest the above, moreover one of the expected changes in EF Core 3.0 is that Backing fields will be used by default.
Anyway, now the problem in question will be solved.
Still, it will be better to follow the good practices. The _workNotes member should be initialized with initializer or in class constructor, and property getter should return it directly. If the idea was to prevent the caller to get access to the private member by casting the result, then there are other ways to prevent that which does not clone the collection content. For instance:
//Aggregated entity
private readonly HashSet<CaseWorkNote> _workNotes = new HashSet<CaseWorkNote>();
public IEnumerable<CaseWorkNote> WorkNotes => _workNotes.Select(e => e);
//
Regardless of whether you keep your current implementation of the navigation property or not, you must let EF Core use the backing field directly.
Add foreign key property CaseId.
Also added Virtual keyword.
public class CaseWorkNote : FullAuditedEntity
{
[ForeignKey("CaseId")]
[Required]
public virtual Case Case { get; private set; }
public virtual Guid CaseId { get; private set; } /* Added */
[Required]
public virtual string Text { get; set; }
private CaseWorkNote() : base() { }
public static CaseWorkNote Create(Case kase, string text)
{
return new CaseWorkNote()
{
Case = kase,
Text = text
};
}
}
i have problem with my project WebApi, by using AutoMapper.
In my solution i use AutoMapper v4.1.1, solution old and have a lot of code inside.
Where is 4 projects
Core - service and extention
DataLayer - DataLayer and repository
MVC - standart web form project
WebApi - new project with Api functionality
in this solution i use:
Entity FrameWork 6.1.1
AutoMapper 4.1.1
connection strings in WebConfig to local DB base on SqlServer.
i have two models, User - its DataBaseEntity, and UserModel - this is BussinesModel
My AutoMapper class in DataLayer looks so
namespace DataLayer
{
internal static class Mapping
{
static Mapping()
{
RegisterMappings();
}
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return Mapper.Map(source, destination);
}
private static void RegisterMappings()
{
try
{
Mapper.AllowNullDestinationValues = true;
//Mapping instruction for User
Mapper.CreateMap<User, UserModel>().IgnoreAllPropertiesWithAnInaccessibleSetter().ReverseMap();
Mapper.AssertConfigurationIsValid();
}
}
}
in CoreLayer i use this code
public AuthenticationResult Login(string login, string password)
{
if (string.IsNullOrEmpty(login))
throw new ArgumentNullException(nameof(login));
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException(nameof(password));
using (var db = new Context())
{
try
{
//Here my UserDbEntity model, and it already connected from Context
var dbUser = db.Users.AsNoTracking().FirstOrDefault(u => u.Login == login && u.IsActive && !u.IsDeleted);
//mapping model
var model = Mapping.Map(dbUser, new UserModel());
//Some result logic here
return result;
}
}
}
all references to Projects inside WebApi and WebFroms Mvc project is same (references to DataLayer and CoreLayer)
and now the problem, than i call action
CoreLayer.Login(string login, string pass)
from my MVC project, he return me correctly data
and if i do so from Web Api project he show me error
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
==================================================================
ServiceModel ->Service (Source member list)
Core.Models.ServiceModel -> Data.DB.Service (Source member list)
Unmapped properties:
Specialization
ServiceTechnic
ServiceTools
RefRescueWorkTypes
all this unmapped members is complex objects (model inside model), and have own rules to mapping. While i working in MVC project, they mappet correctly
P.S Sorry for my English, it`s not my native language
UPD 1 for #Alexandru Marculescu
example of unmmaped models,
Source
PersonalInformation Entity
internal partial class PersonalInformation
{
public override System.Guid Id { get; set; }
public string FullName { get; set; }
//link to related Address entity
public Nullable<System.Guid> AddressId { get; set; }
public string EMail { get; set; }
// a lot of string properties here, nothing intresting
internal virtual Address Address { get; set; }
}
Address entity
internal partial class Address
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Address()
{
this.PersonalInformations = new HashSet<PersonalInformation>();
}
public override System.Guid Id { get; set; }
//hardcoded Id inside code
public System.Guid RegionId { get; set; }
public System.Guid DistrictId { get; set; }
public Nullable<System.Guid> LocalityId { get; set; }
public string Street { get; set; }
public string House { get; set; }
// a lot of string properties here, nothing intresting
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
internal virtual ICollection<PersonalInformation> PersonalInformations { get; set; }
}
bussines model
public class AddressModel
{
public Guid Id {get;set;}
public Guid RegionId { get; set; }
public Guid DistrictId { get; set; }
public Guid? LocalityId { get; set; }
public string Street { get; set; }
public string House { get; set; }
//one-to-many
public List<PersonalInformationModel> PersonalInformationModels {get;set;}
}
public class PersonalInformationModel
{
public Guid Id {get;set;}
public string FullName{ get; set; }
public Guid? AddressId { get; set; }
public string EMail { get; set; }
private AddressModel _address;
public AddressModel Address
{
get
{
if (AddressId.HasValue && App.CreateCommonService().Addresses.Get(AddressId.Value).Value != null)
return App.CreateCommonService().Addresses.Get(AddressId.Value).Value;
return _address;
}
set { _address = value; }
}
}
and mapping instruction for this
internal static class Mapping
{
static Mapping()
{
RegisterMappings();
}
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return Mapper.Map(source, destination);
}
private static void RegisterMappings()
{
try
{
Mapper.AllowNullDestinationValues = true;
Mapper.CreateMap<Address, AddressModel>().ForMember(x=>x.PersonalInformationModels , o=> o.MapFrom(s=>s.PersonalInformations.ToList())).IgnoreAllPropertiesWithAnInaccessibleSetter().ReverseMap();
Mapper.CreateMap<PersonalInformation, PersonalInformationModel>()
.ForMember(x => x.Address, o => o.Ignore()).IgnoreAllPropertiesWithAnInaccessibleSetter().ReverseMap();
Mapper.AssertConfigurationIsValid();
}
catch (Exception ex) when (App.CreateLogService().WriteError(ex))
{
if (Debugger.IsAttached)
Debug.WriteLine(ex);
}
}
}
And this how i call mapping from my services
var item = new PersonalInformationModel();
//some input logic and *item* become full
var dbEntity = Mapping.Map(item, new PersonalInformation());
var item = new AddressModel();
//some input logic and *item* become full
var dbEntity = Mapping.Map(item, new Address());
And for this classes i get error look like this
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
==================================================================
AddressModel ->Service (Source member list)
Core.Models.AddressModel -> Data.DB.Address(Source member list)
Unmapped properties:
PersonalInformations
All references for Linq, Core, Entity frameworks same in both projects, .Net framework version 4.6, connection strings same in web.configs (in both projects) but MVC work good, an Api give me unmapped error.
Suppose you have this source model:
public abstract class SourceModelBase {
}
public class SourceContact : SourceModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; }
public SourceAddress Address { get; set; }
}
public class KeyValuePair { // Not derived from SourceModelBase.
public string Key { get; set; }
public string Value { get; set; }
}
public class SourceAddress : SourceModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Now the destination model should be mapped 1:1 by default (subject to normal AutoMapper configuration), but each thing derived from SourceModelBase should be mapped to a wrapper class class Wrap<T> { T Payload { get; set; } string Meta { get; set; } }.
public abstract class DestinationModelBase {
}
public class DestinationContact : DestinationModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; } // Not wrapped, base class not `SourceModelBase`.
public Wrap<DestinationAddress> Address { get; set; }
}
public class DestinationAddress : DestinationModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Since the contact class itself is derived from SourceModelBase it should be wrapped as well.
The result should have this structure:
Wrap<DestinationContact> Contact
string Meta // Comes from the custom wrapper logic.
DestinationContact Payload
string FirstName
string LastName
KeyValuePair Pair
string Key
string Value
Wrap<DestinationAddress> Address
string Meta // Comes from the custom wrapper logic.
DestinationAddress Payload
string StreetName
string StreetNumber
Obviously this wrapping should nest, illustrated by the fact that the mapped object itself is subject to it and so is its Address property.
For some reason all I keep finding are questions related to mapping from destination to source. I know I have to somehow use ResolveUsing and if the destination type is derived from SourceModelBase, somehow apply custom logic to provide the Wrap<T> value based on the value of the source property.
I don't know where to start at all, though. Especially when the source object itself is specified to be subject of the wrapping logic as well.
What's the best, most AutoMapper-idiomatic way to wrap the nested objects if they meet a condition and at the same time wrap the original object as well if it meets the same condition? I already have the mapper creation abstracted away so I can mold the original object automatically before passing it to the mapper, which may help with subjecting the original object to the resolver as well by doing mapper.Map(new { Root = originalObject }) so the resolver sees the instance of the original object as if it was a value of a property of source object as well, not the source object itself.
According to this issue on AutoMapper GitHub page, there is no direct way to do it.
But there is some workarounds. For example - reflection.
In this case you need to know wrapper type and implement converter for desired types. In this example it's MapAndWrapConverter from TSource to Wrap<TDestination>
CreateWrapMap method creates two bindings:
SourceAddress -> Wrap<DestinationAddress> and SourceContact -> Wrap<DestinationContact> which allow you to map SourceContant to wrapped DestinationContact.
internal class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourceContact, DestinationContact>();
cfg.CreateWrapMap(
//func selecting types to wrap
type => typeof(DestinationModelBase).IsAssignableFrom(type)
&& !type.IsAbstract,
typeof(Wrap<>),
typeof(MapAndWrapConverter<,>));
});
var mapper = config.CreateMapper();
//Using AutoFixture to create complex object
var fixture = new Fixture();
var srcObj = fixture.Create<SourceContact>();
var dstObj = mapper.Map<Wrap<DestinationContact>>(srcObj);
}
}
public static class AutoMapperEx
{
public static IMapperConfigurationExpression CreateWrapMap(
this IMapperConfigurationExpression cfg,
Func<Type, bool> needWrap, Type wrapperGenericType,
Type converterGenericType)
{
var mapperConfiguration =
new MapperConfiguration((MapperConfigurationExpression)cfg);
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var dstType in types.Where(needWrap))
{
var srcType = mapperConfiguration.GetAllTypeMaps()
.Single(map => map.DestinationType == dstType).SourceType;
var wrapperDstType = wrapperGenericType.MakeGenericType(dstType);
var converterType = converterGenericType.MakeGenericType(srcType, dstType);
cfg.CreateMap(srcType, wrapperDstType)
.ConvertUsing(converterType);
}
return cfg;
}
}
public class MapAndWrapConverter<TSource, TDestination>
: ITypeConverter<TSource, Wrap<TDestination>>
{
public Wrap<TDestination> Convert(
TSource source, Wrap<TDestination> destination, ResolutionContext context)
{
return new Wrap<TDestination>
{
Payload = context.Mapper.Map<TDestination>(source)
};
}
}
CreateWrapMap method is a little bit messy, especially the part with finding matching types. But it can be refined according to your needs.
I have the following two classes auto generated with Entity Framework using database first;
public partial class UserXml
{
public UserXml()
{
this.UserXmlHotel = new HashSet<UserXmlHotel>();
}
public long UserId { get; set; }
public string Password { get; set; }
public bool Enabled { get; set; }
public byte FailedAttempt { get; set; }
public virtual User User { get; set; }
public virtual UserXmlIp UserXmlIp { get; set; }
public virtual ICollection<UserXmlHotel> UserXmlHotel { get; set; }
}
and
public partial class UserCustomer
{
public UserCustomer ()
{
this.UserCustomerHotel = new HashSet<UserCustomerHotel >();
}
public long UserId { get; set; }
public bool Enabled { get; set; }
public string Password { get; set; }
}
I've then created the following classes;
public partial class UserXml : IUser
{
}
public partial class UserCustomer : IUser
{
}
public static class EntityExtensions
{
public static IQueryable<T> Enabled<T>(this IQueryable<T> source) where T : IUser
{
return source.Where(x => x.Enabled);
}
}
I have a interface as;
public interface IUser
{
bool Enabled { get; }
}
Want I'm wanting to do is combine the same call for each entity using the same re-usable code; So take
using(var Context = new EscapeEntities())
{
bool bEnabled = Context.UserXml.First(u => u.UserId == iUserId).Enabled;
}
and
using(var Context = new EscapeEntities())
{
bool bEnabled = Context.UserCustomer.First(u => u.UserId == iUserId).Enabled;
}
I'm wanting to use my EntityExtensions class, but I'm having trouble getting it to work. Any pointers ?
When you use expressions on generic types which implement an interface, the expression builder adds a cast to the expression, like below:
x => x.Enabled // converts to something like below:
x => ((IUser)x).Enabled
So what you need to do is to tell the expression builder to not add the cast expression by defining your generic type as a class, which means your Enabled(...) extension method needs to be like this:
public static IQueryable<T> Enabled<T>(this IQueryable<T> source)
where T : class, IUser // added 'class' constraint
{
return source.Where(x => x.Enabled);
}
Another approach (which needs much more work) is to define an ExpressionVisitor class to remove the convert on the expression which is explained on this stackoverflow answer.
So after this correction, you can user your extension method easily:
using(var Context = new EscapeEntities())
{
IQueryable<UserXml> query = Context.UserXml
.Where(u => u.Password == "Foo")).Enabled();
List<UserXml> res = query.ToList();
}
P.S. I think you'd be better off with defining another extra "base" User table with two children named UserXml and UserCustomer and abstract out the common columns into User table, which removes the need for extra interfaces and is also compatible with object oriented design.
I just started using Entity Framework and it created a Context class which I can use to get all the data i need from it. But I am facing an issue on how I should organize my code, by watching the demos, the person just uses the framework and codes everything on a console application. What is the best way to use Entity Framework and that it looks clean?, what I mean by this is...right now using aspx pages, I could just use the aspx.cs to get the data or save the data. But I do not want this, I would like it to be more organized although the Entity Framework did almost everything by creating the objects etc.. but still, I need to use things like
using(var myobject = new MyContextData())
{
blah blah..
}
would you say that it would be nicer to write classes that would wrap these calls?. I would really appreciate any inputs as it would really make me a better programmer using the entity framework.
Regards
This question should everyone, who provides some tutorial about EF, ask. It is hard to say what is the best way, but put all code in the codebehind classes (aspx.cs) does not help extensibility and testability. Please, try to read this article:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Not only it is official tutorial on asp.net, but it mostly shows, that Framework EF could be used correctly in currently fancy Repository pattern
Edit:
I think that Generic Repository is Anti Pattern. But I do not understand #TomTom comment.
Original Answer:
As Radim Köhler mentioned you need to implement Repository and Unit of Work patterns
But the article he provided in my opinion is not fully correct.
At my current job I use following implementation of these patterns.
For example, we have three types of entities: Person, Good and Order. I created repository for Persons. In common case Repository must not be generic. It must contain methods which represent specific queries for this entity. So by looking at the interface of repository you can tell what kinds of queries executed for entity (Person, e.g.). As you will see I created DTO for Person called PersonWrap. For creating PersonWrap from Person and updating Person from PersonWrap you can use AutoMapper instead of PersonWrap() constructor and Update() method. Because EntityFramework DbContext implements Unit of Work pattern, you just need to provide created DbContext to repository methods. If repository method is a separate action and you do not need DbContext outside of this method you can create and dispose it inside this method.
public class Person {
public int Id { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public DateTime RegistrationDate { get; set; }
public List<Order> Orders { get; set; }
}
public class Good {
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Order {
public int Id { get; set; }
public Person Person { get; set; }
public Good Good { get; set; }
public int Count { get; set; }
}
public class MyDbContext: DbContext
{
public IDbSet<Person> Persons { get { return Set<Person>(); }}
public IDbSet<Good> Goods { get { return Set<Good>(); }}
public IDbSet<Order> Orders { get { return Set<Order>(); }}
}
public class PersonRepository {
public IEnumerable<Person> GetAll() {
using (var context = new MyDbContext()) {
return context.Persons.ToList();
}
}
public IEnumerable<Person> GetLastWeekPersons() {
using (var context = new MyDbContext()) {
return context.Persons.Where(p => p.RegistrationDate > new DateTime().AddDays(-7)).ToList();
}
}
public Person GetById(int id, MyDbContext context) {
return context.Persons.Include(p => p.Orders).FirstOrDefault(p => p.Id == id);
}
public Person GetById(int id) {
using (var context = new MyDbContext()) {
return GetById(id, context);
}
}
}
public class PersonWrap {
public int Id { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public int OrderCount { get; set; }
public PersonWrap(Person person) {
Id = person.Id;
FirstName = person.FirstName;
SecondName = person.SecondName;
OrderCount = person.Orders.Count;
}
public void Update(Person person) {
person.FirstName = FirstName;
person.SecondName = SecondName;
}
}
public class PersonDetailsViewController {
public PersonWrap Person { get; protected set; }
public PersonDetailsViewController(int personId) {
var person = new PersonRepository().GetById(personId);
if (person != null) {
Person = new PersonWrap(person);
}
}
public void Save() {
using (var context = new MyDbContext()) {
var person = new PersonRepository().GetById(Person.Id, context);
Person.Update(person);
context.SaveChanges();
}
}
}
You are on the right track for creating classes to handle your EF.
The biggest benefit for doing it this way is able to unit test easily.
Test early and test often is always a good idea.
I suggest putting your EF related classes in a separate project.