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
};
}
}
Related
I have a MySql database with columns Id int and Name:json
Places Table Sample
Id Name
1 {"en":"Sphinx","ar":"أبو الهول","fr":"Le sphinx"}
C# Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
I'm connecting with EntityFramework 6 and connection success and retrieve data like this
{Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" }
What I want how to Map Name to new Object not JSON string
something like this
Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
Localized class
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
when I do this Name property come with NULL value
Code in Repository
using (var context = new PlacesEntityModel())
{
return context.Places.Take(5).ToList();
}
I don't want to use AutoMapper,
I want something in EntityFramework to select only one language in Database Level without fetching all other data and then map it
how to fix this?
You can try extension method to map from your entity type.
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
Extenstion Method ToDto
public static class Extensions
{
public static PlaceDTO ToDto(this Place place)
{
if (place != null)
{
return new PlaceDTO
{
Id = place.Id,
Name = JsonConvert.DeserializeObject<Localized<string>>(place.Name)
};
}
return null;
}
}
Usage
var place = new Place() { Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" };
var placeDTO = place.ToDto();
Console.WriteLine($"{placeDTO.Id}-{placeDTO.Name.ar}-{placeDTO.Name.en}-{placeDTO.Name.fr}");
First of all, by using a class with a property per language, you restrict yourself. You'd always have to add new properties if you add new languages, which would of course be feasible, but unnecessary complicated. Furthermore you'd usually have the language as a string-ish object (or be able to convert), hence this would lead to code like this
Localized<string> name = ...;
switch(language)
{
case "en":
return name.en;
case "ar":
return name.ar;
case "fr":
return name.fr;
default:
throw new LocalizationException();
}
which is error-prone and overly complicated. For your problem, I think I'd opt to use some kind of dictionary
IDictionary<string, string> names = ...;
if(names.ContainsKey(language))
{
return names[language];
}
else
{
throw new LocalizationException();
}
which is easily extensible by just adding more translations to the dictionary.
To convert your JSON string to an IDcitionary<string, string>, you could use the following code
localizedNames = JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
From within your class this would effectively be
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public Dictionary<string, string> LocalizedNames
{
get
{
return JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
}
}
}
The localized values can be accessed like
var localizedPlaceName = place.LocalizedNames[language];
Please note: Depending on your needs and use cases, you should consider the following issues:
Caching
In my snippet, the JSON string is parsed every time the localized names are accessed. Depending on how often you access it, this might be detrimental to performance, which could be mitigated by caching the result (don't forget to delete the cache when Name is set).
Separation of concerns
The class as is is supposed to be a pure model class. You might want to introduce domain classes that encapsulate the presented logic, rather than adding the logic to the model class. Having a factory that creates readily localized objects based on the localizable object and the language could be an option, too.
Error handling
In my code there is no error handling. Depending on the reliability of input you should consider additional error handling.
devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
Like what #Nkosi said
In that case then, take a look at this article devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
It probably can given that the library was able to build that feature in. You would need to figure out what they they did (reverse engineer)
I usually just use JSON.Net, I notice that another answer referenced JObject, but without going into whether your data-model is the right model, I generally find that you can do:
var MyObjectInstance = JObject.Parse(myJsonString).ToObject<MyObjectType>();
I notice that you have ComponentModel attributes on your class. I don't know off hand how many of these JSon.Net supports, and you'd have to research that. It definitely supports some attributes from XML serialization, and also has some of it's own.
Note that you can also convert a JSOn array into a list:
var MyObjectList = JArray.Parse(myJsonString).ToObject<IEnumerable<MyObjectType>();
I want something in EntityFramework to select only one language in
Database Level without fetching all other data and then map it
if you want it to be from database level, you can always create a view and then include this view in your project.
Example :
CREATE VIEW `PlacesLocalized` AS
SELECT
Id
, TRIM(REPLACE(name->'$.en', '"','')) AS en
, TRIM(REPLACE(name->'$.ar', '"','')) AS ar
, TRIM(REPLACE(name->'$.fr', '"','')) AS fr
FROM
places
This would create a model class Like :
public class PlacesLocalized
{
public int Id { get; set; }
public string en {get; set;}
public string ar {get; set;}
public string fr {get; set;}
}
Then, you can do :
var places = context.PlacesLocalized.Where(x=> x.en == "Sphinx");
But if you don't have enough permissions to do this in the database level, then you would need to specify the query in your EF. There is no easy way to change the execution logic of Entity Framework just for specific classes. That's why Entity Framework included SqlQuery method, which would give more flexibility to have custom queries when needed (like yours).
So, if you need to specify the localization from Entity Framework, then you would do a repository class to specify all custom queries you need including creating any DTO needed.
The basic way would be something like this :
public enum Localized
{
English,
Arabic,
French
}
public class PlaceRepo : IDisposable
{
private readonly PlacesEntityModel _context = new PlacesEntityModel();
public List<Place> GetPlacesLocalized(Localized localized = Localized.English)
{
string local = localized == Localized.Arabic ? "$.ar"
: localized == Localized.French ? "$.fr"
: "$.en";
return _context.Places.SqlQuery("SELECT Id, name-> #p0 as Name FROM places", new[] { local })
.Select(x=> new Place { Id = x.Id, Name = x.Name.Replace("\"", string.Empty).Trim() })
.ToList();
}
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
_disposed = true;
}
}
~PlaceRepo()
{
Dispose(false);
}
}
now, you can do this :
using(var repo = new PlaceRepo())
{
var places = repo.GetPlacesLocalized(Localized.Arabic);
}
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public static explicit operator Place(PlaceDTO dto)
{
return new Place()
{
Id = dto.Id,
Name = dto.Name
};
}
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
public static explicit operator PlaceDTO(Place pls)
{
return new PlaceDTO()
{
Id = pls.Id,
Name = pls.Name
};
}
}
var placeDTO = (placeDto)place;
we can achieve this using explicit operator without using auto mapper
I have two entity classes that have a one-to-many relationship.
public class Call : IEntity
{
public int Id { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User : IEntity
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Call> Calls { get; set; }
}
And I have a view model for 'Call' operations on the web layer.
public class CallVm : IViewModel
{
public string Id { get; set; }
public string UserFullname { get; set; }
}
And I use a method to convert my 'Call' object to 'CallVm' object.
This method is briefly as follows.
public CallVm MapCallVm(Call call)
{
return call == null ? null : new CallVm { Id = call.Id, UserFullname = call.User?.Fullname };
}
When I read the 'Call' entity from the database, I sometimes include 'User' and sometimes I don't. When I do not include it, there is no User property definition in the Call object because it is lazy loading. Therefore, I get the following error in MapCallVm method.
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Is there a way to check this? I just want to assign UserFullname = call.User?.Fullname when there is a eager load.
The only solution I can think of is controlling with try-catch. Is there a different solution?
You can use DbReferenceEntry.IsLoaded Property.
Gets or sets a value indicating whether the entity has been loaded
from the database.
if (_dbContext.Entry(Call).Reference(e => e.User).IsLoaded)
Updated
If you are getting value without dbContext, you should force the query to Eager loading instead.
Read the following post to have a better understanding.
Should we disable lazy loading of Entity Framework in web apps?
As #Phong's answer - avoid passing DbContext. Normally, your repository class should map DB entities to simple POCO/DTO objects.
I suggest to introduce mapper class. This will help you to unit test your logic
// Interface to inject to repository
public interface ICallMapper
{
CallVm Map(Call call);
}
public class CallMapper : ICallMapper
{
public CallVm Map(Call call)
{
return call == null ? null : new CallVm { Id = call.Id, UserFullname = call.User?.Username };
}
}
Pass mapper to repository and ensure that your objects are not connected with DB anymore
public class CallRepository : ICallRepository
{
private readonly ICallMapper _callMapper;
public CallRepository(ICallMapper callMapper)
{
_callMapper = callMapper;
}
public IList<CallVm> GetList()
{
// Call DB and get entities
var calls = GetCalls();
// Map DB entities to plain model
return calls.Select(_callMapper.Map).ToList();
}
}
This lets you to get rid of your error. And makes your program more structable and testable.
I have basic object models with cross references
//Model in which I pass and gather data from view
public class ItemModel
{
public BasicItem BasicItem;
public FoodItem FoodItem;
public LocalItem LocalItem;
public ItemModel()
{
BasicItem = new BasicItem();
FoodItem = new FoodItem();
LocalItem = new LocalItem();
}
}
//And classes represents EF entities
public class BasicItem
{
...//Multiple basic fields: int, string
//EF references for PK-FK connection
public FoodItem FoodItem { get; set; }
public LocalItem LocalItem { get; set; }
}
public class LocalItem
{
...//Multiple basic fields: int, string
//EF reference for PK-FK connection
public BasicItem BasicItem { get; set; }
}
public class FoodItem
{
...//Multiple basic fields: int, string
//EF reference for PK-FK connection
public BasicItem BasicItem { get; set; }
}
And my view in basics seems like this
#model ItemModel
...
<input required asp-for="BasicItem.Price" type="number" name="Price">
...
<input asp-for="FoodItem.Weight" type="number" name="Weight">
...
As now I connect it (so different entities have relation each to other) like this:
public async Task<IActionResult> ProductAdd(ItemModel ItemModel)
{
if (ItemModel.BasicItem != null)
{
if (ItemModel.LocalItem != null)
{
ItemModel.BasicItem.LocalItem = ItemModel.LocalItem;
ItemModel.LocalItem.BasicItem = ItemModel.BasicItem;
await db.LocalItems.AddAsync(ItemModel.LocalItem);
}
//same for FoodItem
await db.BasicItems.AddAsync(ItemModel.BasicItem);
await db.SaveChangesAsync();
}
}
But data from form dosent bind to my ItemModel, so my code fails at point when it trying to Add new entity to db, but it has null fields(which null by default, but setuped in form).
Is there any way I can help bind this model to data Im entering?
As other way I can only see this: create plain model which will have all fields from Basic, Local and Food items and bind it in my action. But it will hurt a much, if I ever wanted to change one of this classes.
For you scenario , BasicItem has a one-to-one relationship with LocalItem and FootItem.When adding data into the database , you need to pay attention to that if the foreign key is nullable or exists and the order in which data is added to the primary table and child table .
Here is a working demo ,you could refer to :
Model definition
public class BasicItem
{
public int BasicItemID { get; set; }
public string Name { get; set; }
public int FoodItemID { get; set; }
[ForeignKey("FoodItemID")]
public FoodItem FoodItem { get; set; }
public int LocalItemID { get; set; }
[ForeignKey("LocalItemID")]
public LocalItem LocalItem { get; set; }
}
public class FoodItem
{
public int FoodItemID { get; set; }
public string Name { get; set; }
//public int BasicItemID { get; set; }
public BasicItem BasicItem { get; set; }
}
public class LocalItem
{
public int LocalItemID { get; set; }
public string Name { get; set; }
//public int BasicItemID { get; set; }
public BasicItem BasicItem { get; set; }
}
public class ItemModel
{
public BasicItem BasicItem;
public FoodItem FoodItem;
public LocalItem LocalItem;
public ItemModel()
{
BasicItem = new BasicItem();
FoodItem = new FoodItem();
LocalItem = new LocalItem();
}
}
Controller
public async Task<IActionResult> ProductAdd(ItemModel ItemModel)
{
if (ItemModel.BasicItem != null)
{
if (ItemModel.LocalItem != null)
{
await db.LocalItems.AddAsync(ItemModel.LocalItem);
await db.FoodItems.AddAsync(ItemModel.FoodItem);
}
//same for FoodItem
ItemModel.BasicItem.LocalItem = ItemModel.LocalItem;
ItemModel.BasicItem.FoodItem = ItemModel.FoodItem;
await db.BasicItems.AddAsync(ItemModel.BasicItem);
await db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(ItemModel);
}
Okay, we can divide my situation into 2 basic cases:
Creating new entities
Updating entities
In first case it's pretty simple and easy cause you can create new object, fill it up, setup relations (you can only setup relation in one object like basicItem.FoodItem = foodItem, you don't need to do foodItem.BasicItem = basicItem, cause EF will automatically connect them) and send it to db, and it will work.
In second case, it's a little more complicated, cause in case to update data in db, you must get a related entity to a context. It's brings it's own limitations. And again you can have two approaches:
Create new object and manually (or through auto-mapper, but I didn't dig into this) overwrite fields of db related object at the end.
Fetch object from db at the beginning, pass it it through actions and change data on fly (if you want/need, you can even update db record on fly).
They are quite the same in a way, that you need to choose what field to update and write some code dbFoodItem.Weight = userInput.Weight.
So in my case I took second approach, and cause I collected data in multiple actions, I used session to data storage object between them.
I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.
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