Initializing the mapper
public static class MapperConfig
{
public const int MAX_MAPPING_DEPTH = 2;
private static MapperConfiguration _config;
private static IMapper _mapper;
public static IMapper Mapper => _mapper ?? (_mapper = GetConfig().CreateMapper());
public static MapperConfiguration GetConfig()
{
var assembly = Assembly.GetExecutingAssembly();
if (_config != null)
return _config;
_config = new MapperConfiguration(cfg =>
{
cfg.AddProfiles(assembly);
cfg.ForAllMaps(ConfigTypeMapping);
});
_config.AssertConfigurationIsValid();
return _config;
}
public static void ConfigTypeMapping(TypeMap map, IMappingExpression expression)
{
map.ShouldCheckForValid();
expression.PreserveReferences();
expression.MaxDepth(MAX_MAPPING_DEPTH);
//expression.ForAllMembers(m => m.UseDestinationValue());
}
}
The main entity
[ResourceKey("MEDIA_ITEM")]
public class MediaItem : SocialEntity
{
/// <summary>
/// User-defined media item <see cref="Title"/> or its original file name.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The original <see cref="FileName"/>.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// The <see cref="FileGuid"/> that refers to the CDN file entry.
/// </summary>
public Guid FileGuid { get; set; }
/// <summary>
/// The purpose / group (aka <see cref="Type"/>) for this <see cref="MediaItem"/>.
/// </summary>
public FileType Type { get; set; }
/// <summary>
/// Allows limiting retrieval of this <see cref="MediaItem"/> depending on a specific <see cref="PrivacyLevel"/>.
/// </summary>
public PrivacyLevel PrivacyLevel { get; set; }
/// <summary>
/// The <see cref="Source"/> for the Content / File of this <see cref="MediaItem"/>.
/// </summary>
public virtual Url Source { get; set; }
public Guid? SourceGuid { get; set; }
/// <summary>
/// The <see cref="Profile"/> entity that this media file is bound to.
/// </summary>
public virtual Profile Profile { get; set; }
public Guid? ProfileGuid { get; set; }
/// <summary>
/// The <see cref="Article"/> entity that this media file is bound to.
/// </summary>
public virtual Article Article { get; set; }
public Guid? ArticleGuid { get; set; }
/// <summary>
/// The <see cref="Communication.Comment"/> entity that this media file is bound to.
/// </summary>
public virtual Comment Comment { get; set; }
public Guid? CommentGuid { get; set; }
/// <summary>
/// The <see cref="Theme"/> entity that this media file is bound to.
/// </summary>
public virtual Theme Theme { get; set; }
public Guid? ThemeGuid { get; set; }
}
As you can see, the entity inherits from the SocialEntity that holds a bunch of default collections. The SocialEntity then inherits the base Entity class with the Id, user, date created etc.
Base class mapping
CreateMap<SocialEntity, SocialDomainModel>()
.IncludeBase<Entity, DomainModel>()
.ReverseMap()
.IncludeBase<DomainModel, Entity>();
CreateMap<Entity, DomainModel>()
.IncludeBase<global::Data.Pattern.Entity.Entity, DomainModel>()
.ReverseMap()
.IncludeBase<DomainModel, global::Data.Pattern.Entity.Entity>();
CreateMap<global::Data.Pattern.Entity.Entity, DomainModel>()
.ForMember(dm => dm.Creator, mo => mo.Ignore())
.ForMember(dm => dm.CreatorGuid, mo => mo.Ignore())
.ReverseMap();
CreateMap<MediaItem, Domain.Models.Storage.MediaItem>()
.IncludeBase<SocialEntity, SocialDomainModel>()
.ReverseMap()
.IncludeBase<SocialDomainModel, SocialEntity>();
The test method
[TestMethod]
public void MapMediaTest()
{
var m = MapperConfig.Mapper;
var entity = new MediaItem();
var entityToDomain = m.Map<Domain.Models.Storage.MediaItem>(entity);
Assert.IsTrue(entityToDomain != null);
}
The result
As you can see, the memory floods after ~20 seconds.
It looks like automapper is configured wrong somewhere that causes an infinite loop and results in a StackOverflowException.
My attempt
I've tried every single entity, always same result.
Switched between 5.1.1 and 6.0.2 a bunch of times.
Tried a lot of different configurations.
Switched HashSet collections to normal ICollections.
I'm mapping between entities and domain models, no projection / querying used.
Every entity is retrieved before mapping, no lazy loading.
Note, I do have virtual keywords with other entities and classes and every collection is initialized by default.
I'm out of options, I can't think of what to do besides ditching (not-so)AutoMapper for something else. I'd love some extra insight, thanks!
Edit
The Article class.
/// <summary>
/// Used to store <see cref="Article"/> data.
/// </summary>
[ResourceKey("ARTICLE")]
public class Article : SocialEntity
{
/// <summary>
/// The main <see cref="Title"/> of this <see cref="Article"/>.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// The content <see cref="Body"/> of this <see cref="Article"/>.
/// </summary>
public string Body { get; set; } = string.Empty;
/// <summary>
/// The <see cref="ShortDescription"/> of this <see cref="Article"/>.
/// </summary>
public string ShortDescription { get; set; } = string.Empty;
/// <summary>
/// The long <see cref="Description"/> of this <see cref="Article"/>.
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// The <see cref="DateTime"/> that the <see cref="Entity"/> will be available to use.
/// </summary>
public DateTime ExpiresOn { get; set; }
public virtual HashSet<Url> Sources { get; set; } = new HashSet<Url>();
public virtual HashSet<MediaItem> Media { get; set; } = new HashSet<MediaItem>();
public virtual HashSet<Tag> Tags { get; set; } = new HashSet<Tag>();
public virtual HashSet<Hobby> Hobbies { get; set; } = new HashSet<Hobby>();
}
#Richard's answer got me on the right track! Too many navigational properties...
AutoMapper couldn't figure out how to map them all, probably missing configurations.
Entity Framework didn't have any problem with them, just AutoMapper.
I removed most of the navigational properties and it seemed that I didn't need most of them. This solved my issue the quick & easy way!
In my experience with Automapper, you get StackOverflowException when mapping circular references.
I can see that MediaItem has an "Article" property, and Article has a collection of MediaItems.
If your Domain models also have equivalents of these navigation properties, do you need them both ways?
In my case I had a structure like this in my system (both DTO and entity models were the same). There were lists of plans and only in some of them it would throw StackOverflowException. Entity Framework was doing fine having multiple levels of nesting like this, but AutoMapper would fail at ~3 levels. Fortunately the PreviousPlan property was not needed so I just removed it and it worked fine. If you can't afford removing it I would suggest playing with Automapper's MaxDepth.
I know it's not really an answer but maybe will help somebody find the cause.
public class Plan
{
public int Id { get; set; }
public int? PreviousPlanId { get; set; }
public Plan PreviousPlan { get; set; }
...
}
Related
Good night!
I am playing with EF7 to learn about it.
I've created the next classes:
public class Lawsuit
{
/// <summary>
/// Lawsuit identificator.
/// </summary>
public Guid LawsuitId { get; set; }
/// <summary>
/// Date time asigned where lawsuit was created.
/// </summary>
public DateTime DateTimeCreated { get; set; }
/// <summary>
/// First part envolved signature object.
/// </summary>
public virtual Signature? FirstPartyEnvolved { get; set; }
/// <summary>
/// First part envolved signature object.
/// </summary>
public virtual Signature? SecondPartyEnvolved { get; set; }
/// <summary>
/// Winner position indicator
/// </summary>
public WinnerPosition? WinnerPosition { get; set; }
}
public class Signature
{
/// <summary>
/// Id from signature.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// String which saves text received from contract signatures.
/// </summary>
public string? Signatures { get; set; }
}
At insert, the data is created correctly at database (SQL Server), but when I try to obtain the data, FirstPartyEnvolved and SecondPartyEnvolved attributes are empty.
Example of response:
[
{
"lawsuitId": "6ece06da-d5f9-40e7-a7a3-2816618e7869",
"dateTimeCreated": "2022-12-12T22:05:24.4717517",
"firstPartyEnvolved": null,
"secondPartyEnvolved": null,
"winnerPosition": 1
}
]
I was checking if the DTO object was not parsing well the data but the problem seems to be at the Entity object
public Task Handle()
{
var Lawsuites_repo = LawsuitRepository.GetAllLawsuit();
var Lawsuites = Lawsuites_repo.Select(x =>
new LawsuitDTO
{
LawsuitId = x.LawsuitId,
DateTimeCreated = x.DateTimeCreated,
WinnerPosition = x.WinnerPosition ?? 0,
FirstPartyEnvolved = x.FirstPartyEnvolved,
SecondPartyEnvolved = x.SecondPartyEnvolved
});
GetAllLawsuitsOutputPort.Handle(Lawsuites);
return Task.CompletedTask;
}
I've tried to changed the Signatures objects for GUID and I am receiving well this data. Seems a problem parsing the object from the EF context to the Entity class.
You haven't detailed what is in your GetAllLawsuit() method, but EF does not eager load related entities by default, so you are likely just ToList()'ing the content, then the collection will be loaded from the database with null references for the Signatures
If you remove the call to GetAllLawsuit() and use the Select projection directly from the DbContext, then it will work.
You can also force EF to eager load your related entities by using the Include extension method in your GetAllLawsuit method.
My main object, has a property which is a List of tags
[SharedCosmosCollection("shared")]
public class GlobalPageTemplate : ISharedCosmosEntity
{
/// <summary>
/// Id
/// </summary>
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Cosmos Entity name
/// </summary>
[CosmosPartitionKey]
public string CosmosEntityName { get; set; }
/// <summary>
/// Page name
/// </summary>
public string ExtractedPageName { get; set; }
/// <summary>
/// Site collection Template Name
/// </summary>
public string ExtractedSitecollectionTemplateName { get; set; }
/// <summary>
/// GlobalDesignTenantId
/// </summary>
public string ExtractedGlobalDesignTenantId { get; set; }
/// <summary>
/// Global design tenant site collection url
/// </summary>
public string ExtractedGlobalDesigntenantSiteCollectionUrl { get; set; }
/// <summary>
/// Page template picture Url
/// </summary>
public string PageTemplatePictureUrl { get; set; }
/// <summary>
/// Base64 image of the page template
/// </summary>
public string Base64Image { get; set; }
/// <summary>
/// Name of the template
/// </summary>
public string PageTemplateName { get; set; }
/// <summary>
/// Page sections
/// </summary>
public List<Section> Sections { get; set; }
/// <summary>
/// Tags
/// </summary>
public List<Tag> Tags { get; set; }
}
Tag object is here:
public class Tag : ISharedCosmosEntity
{
/// <summary>
/// Id
/// </summary>
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Tag name
/// </summary>
public string TagName { get; set; }
/// <summary>
/// cosmos entity name
/// </summary>
[CosmosPartitionKey]
public string CosmosEntityName { get; set; }
}
In my WebAPI, from the frontend, I might get duplicate tags,
how do I remove them and leave a clean list of tags before saving?
Can I suggest altering your data structure that stores your tags to a HashSet? If so, you can then do something like this.
A HashSet is an unordered collection of unique elements. It is generally used when we want to prevent duplicate elements from being placed in a collection. The performance of the HashSet is much better in comparison to the list.
Essentially, you supply a custom IEqualityComparer to your HashSet on initialization.
public class TagComparer : IEqualityComparer<Tag>
{
public bool Equals(Tag x, Tag y)
{
return x.Id.Equals(y.Id, StringComparison.InvariantCultureIgnoreCase);
}
public int GetHashCode(Tag obj)
{
return obj.Id.GetHashCode();
}
}
And then you can do
HashSet<Tag> Tags = new HashSet<Tag>(new TagComparer());
In general, I always try to use data structures that make sense for the problem at hand. If you know you'll always want this collection to have unique elements, then I suggest you use a HashSet.
If you can't use a HashSet and you want to stick with a list, you can use linq's Distinct method on your Tags list and pass in the TagComparer object from above .
List<Tag> DistinctTagList = Tags.Distict(new TagComparer())
Not exactly an answer to your question (the other answers are all valid solutions for that), but if for some reason you're looking to actually extract your duplicate objects, such as for debugging, error processing, whatever, I wanted to offer the below.
var duplicates = someList
.GroupBy(r => r.Id)
.Where(g => g.Count() > 1)
.ToList();
Then you have a slightly different way to manage your list from pure distinct
someList = someList.Except(duplicates).ToList();
Which is then a list of keys which had no duplicates.
what you are looking for is probably the distict method:
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinct?view=netframework-4.8
for that you would also need to write an IEqualityComparer, which can simply compare by property https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iequalitycomparer-1?view=netframework-4.8
Then you could call it on your Enumerable:
var distinctTags = Tags.Distict(new TagEqualityComparer)
And the equalityComparer:
class TagEqualityComparer : IEqualityComparer<Tag>
{
public bool Equals(Tag t1, Tag t2)
{
if (t2 == null && t1 == null)
return true;
else if (t1 == null || t2 == null)
return false;
else if(t1.Id == t2.Id)
return true;
else
return false;
}
public int GetHashCode(Tag t)
{
// any custom hashingfunction here
}
}
Using only linq you can do this:
If tags have unique ids:
tags.GroupBy(x => x.Id).Select(x => x.First()).ToList();
If you need to compare all columns:
tags.GroupBy(x => new {x.Id, x.TagName, x.CosmosEntityName}).Select(x => x.First()).ToList();
im using Entity Framework6 in my solution, and now i need to track changes in database but cant figure out how to do it.
Is there a way to track changes in database using Entity Framwork?
update:
I've found this solution, its using SqlDependency in the way that allows to use it With Entity framework.
http://www.codeproject.com/Articles/233770/AutoRefresh-Entity-Framework-data-using-SQL-Server
I've found this solution, its using SqlDependency in the way that allows to use it With Entity framework.
http://www.codeproject.com/Articles/233770/AutoRefresh-Entity-Framework-data-using-SQL-Server
You can overwrite the SaveChanges() method to track the changes. Here's some example out of one of our applications:
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var entries = ChangeTracker.Entries<IAuditable>();
if (entries != null)
{
foreach (DbEntityEntry<IAuditable> entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.SystemFields = new SystemFields
{
SysActivityCode = ActivityCode,
SysCreationUser = UserId,
SysCreationDate = DateTime.UtcNow
};
break;
case EntityState.Modified:
entry.Entity.SystemFields.SysModificationDate = DateTime.UtcNow;
entry.Entity.SystemFields.SysModificationUser = UserId;
entry.Entity.SystemFields.SysActivityCode = ActivityCode;
break;
}
}
}
return base.SaveChanges();
}
where SystemFields is a ComplexType added to all entries which implement the IAuditable interface:
public interface IAuditable
{
/// <summary>
/// System fields who contain change and status information
/// </summary>
SystemFields SystemFields { get; set; }
}
[ComplexType]
public class SystemFields
{
/// <summary>
/// When has this entry be created
/// </summary>
[Required]
public DateTime SysCreationDate { get; set; }
/// <summary>
/// Who has created the entry
/// </summary>
[Required]
public string SysCreationUser { get; set; }
/// <summary>
/// When has this entry been modified
/// </summary>
public DateTime? SysModificationDate { get; set; }
/// <summary>
/// Who has updated the entry
/// </summary>
[CanBeNull]
public string SysModificationUser { get; set; }
/// <summary>
/// Which activity has created/changed this entry
/// </summary>
[Required]
[StringLength(32)]
public string SysActivityCode { get; set; }
}
I have call a list of phone records in wcf service. Now i need to add comments & feedback against all records, in such a way that user clicks the notes button and then enter the comments and feedback aaisnt a specific record.
Here is my class FOR notes
public class CallNote
{
public int Id { get; set; }
///<Summary>
///
///</Summary>
public string Comments { get; set; }
///<Summary>
///
///</Summary>
///
public string FeedBack { get; set; }
///<Summary>
///
///</Summary>
///
public string Login { get; set; }
}
Class for callhistory
public class CallHistory
{
/// <summary>
///
/// </summary>
///
public int CallId { get; set; }
/// <summary>
///
/// </summary>
///
public string Login { get; set; }
/// <summary>
///
/// </summary>
public string FirstName { get; set; }
/// <summary>
///
/// </summary>
public string LastName { get; set; }
/// <summary>
///
/// </summary>
public string Email { get; set; }
/// <summary>
///
/// </summary>
public string Country { get; set; }
/// <summary>
///
/// </summary>
public string Phone { get; set; }
/// <summary>
///
/// </summary>
public string DialedNumber { get; set; }
/// <summary>
///
/// </summary>
public string CallStart { get; set; }
/// <summary>
///
/// </summary>
public string CallEnd { get; set; }
/// <summary>
///
/// </summary>
public string TariffDescription { get; set; }
/// <summary>
///
/// </summary>
}
and here is my controller
public ActionResult CreateNote()
{
var result = Manager.GetUsersWhoHaveConsumedFreeCredit();
JavaScriptSerializer serializer = new JavaScriptSerializer();
var callHistory = serializer.Deserialize<List<CallHistory>>(result);
return View(callHistory.ToPagedList(1, Settings.PagingSize));
}
[HttpPost]
public ActionResult CreateNote(CallNote callnote)
{
CallNote_Db db = new CallNote_Db();
if (ModelState.IsValid)
{
db.callnote.Add(callnote);
db.SaveChanges();
return RedirectToAction("CallHistory");
}
return PartialView("CallHistory", callnote);
}
public ActionResult UsersWhoHaveConsumedFreeCredit(int Id = 1)
{
var result = Manager.GetUsersWhoHaveConsumedFreeCredit(); ;
JavaScriptSerializer serializer = new JavaScriptSerializer();
var callHistory = serializer.Deserialize<List<CallHistory>>(result);
CallNote callNote = new Models.CallNote();
CallHistoryFilter callHistoryFilter = new Models.CallHistoryFilter();
MyYelloAdminEntities db = new Models.MyYelloAdminEntities();
{
callHistoryFilter.Countries = db.Countries.ToList();
callHistoryFilter.Countries.Insert(0, new Country { Title = "All", Prefix = "-1" });
var model = new UsersWhoHaveConsumedFreeCredit
{
CallHistory = callHistory.ToPagedList(Id, Settings.PagingSize),
CallHistoryFilter = callHistoryFilter
};
return View(model);
}
}
How i can add comments against each record.
If you want to combine two different WCF data contracts for your UI/display convenience,
then you can create a view model in your MVC project that is composed of these two datacontracts.
And once after you fetch WCF contracts to your MVC app then crate a datamapper(perhaps by using Automapper or a mapper of your choice) between WCF to your ViewModel and reverse the process while pushing data from UI back to WCF service.
Update based on your comments:
Please take a look at AutoMapper, it is a mapping tool to copy your property data from one type of entity to other type based on the mapping strategy you defined.
If you need to maintain comments/feedback at the currently logged in user then, you can track that on server and you need to only get the new comments from UI to controller and prepare the Model to save accordingly.
If you need to maintain comments/feedback at the respective user(the registered user who is providing the comments/feedback against your content) level, then you can maintain a hidden field on view and then you should post the userid and comments back to controller and save them.
So I have a ViewModel for searching my database. I intend to refactor away the result set collection. I don't think it belongs in this ViewModel. All I want to do is when the user submits the page is compare the ViewModel passed to my Controller Action with the ViewModel stored in TempData. So I can tell if it is the SameSearch.
So how could that be done? considering I have many many many properties in my viewModel I'd prefer not to compare one by one. And using Json.Encode to serialize them and compare seems "hacky" to me.
Also, if I do have to compare one by one. Should that be done by overriding .Equals()
controller action Search()
public ActionResult Search(SearchViewModel model)
{
SearchViewModel savedSearch = null;
if (Request["submit"].Equals("reset",StringComparison.OrdinalIgnoreCase))
{
TempData.Remove("filter");
model = new SearchViewModel();
}
else if (TempData.ContainsKey("filter"))
{
savedSearch = (SearchViewModel)Convert.ChangeType(TempData["filter"], typeof(SearchViewModel));
}
var currentmodel = System.Web.Helpers.Json.Encode(model);
var savedmodel = System.Web.Helpers.Json.Encode(savedSearch);
if (savedSearch == null)
{
model.NewSearch = true;
}
//The search hasn't changed.
if(currentmodel == savedmodel)
{
model.isSameSearch = true;
}
else
{
model.isSameSearch = false;
}
//do more stuff
//return view
}
ViewModel SearchViewModel
public class SearchViewModel
{
public SearchViewModel()
{
Page = 1;
BuildingSearch = new SearchBuildingViewModel();
ParcelSearch = new SearchParcelViewModel();
SalesSearch = new SearchSalesViewModel();
PropertyFeatureSearch = new SearchPropertyFeaturesViewModel();
ValuesSearch = new SearchValuesViewModel();
}
/// <summary>
/// Gets or sets a value indicating whether use advanced search.
/// </summary>
public bool UseAdvancedSearch { get; set; }
public bool NewSearch { get; set; }
public bool isSameSearch { get; set; }
/// <summary>
/// Gets or sets the page.
/// </summary>
[HiddenInput]
[ScaffoldColumn(false)]
public int Page { get; set; }
[HiddenInput]
[ScaffoldColumn(false)]
public string SortOption { get; set; }
#region Search View Models
/// <summary>
/// Gets or sets the building search.
/// </summary>
public SearchBuildingViewModel BuildingSearch { get; set; }
/// <summary>
/// Gets or sets the parcel search.
/// </summary>
public SearchParcelViewModel ParcelSearch { get; set; }
/// <summary>
/// Gets or sets the property feature search.
/// </summary>
public SearchPropertyFeaturesViewModel PropertyFeatureSearch { get; set; }
/// <summary>
/// Gets or sets the sales search.
/// </summary>
public SearchSalesViewModel SalesSearch { get; set; }
/// <summary>
/// Gets or sets the values search.
/// </summary>
public SearchValuesViewModel ValuesSearch { get; set; }
#endregion
/// <summary>
/// Gets or sets the search results.
/// </summary>
[ScaffoldColumn(false)]
public IPagination<ParcelResultItemViewModel> SearchResults { get; set; }
}
Implement IEquatable<T> in your ViewModel where T is the ViewModel class name, and create your own Equals method logic:
public bool Equals(ViewModel other)
{
//compare properties, etc here
}
And override GetHashCode():
public override int GetHashCode()
{
//you custom hash code algorithm
}
Consider using http://comparenetobjects.codeplex.com/ which will do a deep compare of objects. You can even use NuGet to get it: http://www.nuget.org/packages/CompareNETObjects