I tried creating a mapping to a string using the following CreateMap():
Mapper.CreateMap<MyComplexType, string>()
.ConvertUsing(c => c.Name);
But when I try to use this mapping, I get the following error:
Type 'System.String' does not have a default constructor
That makes sense, but I've been reading around and supposedly this should work. Is there something else I have to do?
In my case I was using
.ProjectTo<>()
To project directly from a DBContext collection (EF 6) to my DTO e.g.
db.Configuration.LazyLoadingEnabled = false;
prospects = db.Prospects.Where([my where lambda]).ProjectTo<ProspectDTO>().ToList();
With a destination with an IEnumerable<string> property coming from a M-M related table i.e.
public class ProspectDTO
{
public IEnumerable<string> Brands { get; set; }
}
and my solution was mapping as follows
AutoMapper.Mapper.CreateMap<Prospect, ProspectDTO>().ForMember(dest => dest.Brands, opts => opts.MapFrom(src => src.Brands.Select(b => b.Name)));
N.B. I am using the ProjectTo<> like this to avoid the common lazy loading select n+1 problem and ensure decent (quick) sql runs against the DB, and I have all the related table data I need. Excellent.
Thanks Jimmy Bogard you rockstar !!!
Related
I develop a simple application, something like a chat where every message may contain text and files.
Entities are related like this:
Message group -> every message group has a collection of messages -> every message has a property 'FileCollection' -> 'File collection' has 4 collections: Images, Video, Audio, Files. All of them has the same relations in database. To show this logic here is my query to get all message groups with their entities:
var messageGroups = await _db.MessageGroups
.Where(mg => mg.UserId == id)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Images)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Video)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Audio)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Files)
.ToListAsync();
The problem is that every type of file (Image, Audio etc.) has a 'Data' column in Db (property in EF Core) which contains their blob data. I want to exclude all blob from query, because query becomes extremely heavy loading all user files from Db. Something like this (but exclude method does not exist):
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Video).exclude(video => video.Data);
Is there any way to use explicit loading at the end of the query? Or maybe there are attributes like [JsonIgnore] which excludes class property from Json serializing? Or any other method?
If it helps: ImageFile, AudioFile and others inherit from File super class:
public class File
{
[Column("id")]
public int Id { get; set; }
[Column("content_type")]
public string ContentType { get; set; }
[Column("file_name")]
public string FileName { get; set; }
[Column("length")]
public long Length { get; set; }
[Column("related_file_collection_id")]
public int FileCollectionId { get; set; }
public FileCollection FileCollection { get; set; }
}
public class ImageFile : File
{
[Column("data")]
public byte[] Data { get; set; }
}
I need all properties from 'File' class without 'Data' property from it's child classes.
I believe the best way would be to configure your DbContext for those entities containing Blob columns using Table Splitting.
Don't let the name confuse you. This technique is not to move the Blob to a different table. Instead, it will allow you to fit two "entities" on the same row.
In your case, you could split your File from your FileData, meaning that you will have a different entity for each of them, but both will be stored on the same row on the same table.
By using table splitting, you can .Include your File and it won't include the FileData unless you explicitly tell EF Core to do it.
If you don't wanna go down that road, I believe that you would either need to write some custom Selects or custom SQL.
You can use the [NotMapped] attribute but then you will not be able to retrive that column from the db from other queries.
You can also create a DTO and select only the required properties, but that would not be elegant considering all your includes.
As it was advised here, Table splitting can be the answer, but it is a little complicated. I've just modified my query using .Select(). Not very elegant, also I have a cycle inside cycle, but it works:
List<MessageGroup> messageGroups = await _db.MessageGroups.Where(mg => mg.UserId == id).AsNoTracking().AsSplitQuery().Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Images)
.Include(m => m.Messages).ThenInclude(mes => mes.UrlPreviews).ToListAsync();
foreach (var mg in messageGroups)
{
foreach (var m in mg.Messages)
{
m.FileCollection.Video = await _db.Video.Where(video => video.FileCollectionId == m.FileCollection.Id).Select(v => new VideoFile(v.ContentType, v.FileName, v.Length, v.FileCollectionId, null)).ToListAsync();
m.FileCollection.Audio = await _db.Audio.Where(audio => audio.FileCollectionId == m.FileCollection.Id).Select(a => new AudioFile(a.ContentType, a.FileName, a.Length, a.FileCollectionId, null)).ToListAsync();
m.FileCollection.Files = await _db.Files.Where(file => file.FileCollectionId == m.FileCollection.Id).Select(f => new OtherFile(f.ContentType, f.FileName, f.Length, f.FileCollectionId, null)).ToListAsync();
}
}
null in file constructors is where byte[] blob data should be.
One other solution is to (re-)construct your entity using Select.
_dbContext.MyEntity.Select(
myEntity => new MyEntity()
{
Property1 = myEntity.Property1,
Property2 = myEntity.Property2
// do not select / load Property3
});
EF Core will translate this query in such a way that in the SELECT SQL-statement it only accesses the fields used in your query. This way, the other large columns are not loaded from the database.
However, this solution only works if you have a suitable class constructor or you can set properties to empty or dummy values, and is therefore not generally applicable.
I don't think using TPH or TPT inheritance method influence on this.
My goal is to have a single method that load everything from the database with mixed types of entities that may have separate relationship depending on the type.
Let's take this code-first model (simplistic mock-up to represent my problem):
public abstract class Entity
{
public int ID { get; set; }
public string Name { get; set; }
}
public abstract class EntityWithInfo : Entity
{
public AdditionalInformation Info { get; set; }
}
public class DerivedEntityWithInfo : EntityWithInfo
{
}
public class DerivedEntityWithInfo2 : EntityWithInfo
{
}
public class DerivedEntityWithoutInfo : Entity
{
}
public class AdditionalInformation
{
public int ID { get; set; }
public int SomeProperty { get; set; }
}
And the Fluent API Configuration:
modelBuilder.Entity<Entity>()
.HasKey(e => e.ID)
.Map<DerivedEntityWithInfo>(m => m.Requires("Type").HasValue(1)
.Map<DerivedEntityWithInfo2>(m => m.Requires("Type").HasValue(2)
.Map<DerivedEntityWithoutInfo>(m => m.Requires("Type").HasValue(3);
modelBuilder.Entity<EntityWithInfo>()
.HasRequired(e => e.Info)
.WithRequiredPrincipal()
.Map(e => e.MapKey("Entity_FK"));
modelBuilder.Entity<AdditionalInformation>()
.HasKey(e => e.ID);
Or visually:
With the SQL schema being simple:
Table Entity with: Id, Type, Name
Table AdditionalInformation with: Id, SomeProperty, Entity_FK
Now, I want to be able to do something like:
context.Entity.Where(t => t.ID = 304 || t.ID = 512).ToList();
This gives me correctly the list of all entities, and properly typed. But, of course the Info property is always null. Disabling LazyLoading and removing virtual don't force to load it either, as I understood I absolutely need to have a .Include(t => t.Info) line in there.
I know I can call
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Where(t => t.ID = 304 || t.ID = 512).ToList();
But then I will only get entities derived of EntityWithInfo, and not the DerivedEntityWithoutInfo ones.
So let's try with an union:
context.Entity.OfType<EntityWithInfo>().Include(t => t.Info).Cast<Entity>()
.Union(context.Entity.OfType<DerivedEntityWithoutInfo>().Cast<Entity>())
.Where(t => t.ID == 719 || t.ID == 402);
This does not work, it tells me "Entity does not declare navigation property Info".
And also, I guess this would create one hell of a SQL query.
In fact, the very reason why I'm on this is because of a very old project that was using LINQ2SQL doing the equivalent with "LoadWith" options generating an abusive SQL query (duplicate the relationship table in a join for every inherited type). Loading a single entity take more times than loading the entire table of thousands of elements raw without hierarchy. So I was trying to see if porting to EntityFramework would generate a more optimal SQL query.
So, is there a way to do this or we are simply trying to do something the wrong way? This doesn't seems to be a very popular method to have both inheritance, relationship on derived classes, and eager loading because I find pretty much no resource on that online.
At this point, any suggestion on how to create this object model from this database model would be appreciated. Thanks
This is now supported in EF Core 2.1. Now to see if the resulting query is not too much performance-hungry.
I have an entity and a corresponding DTO
public class PersonEntity {
public int personId;
public List<Contact> contacts;
}
public class PersonDto {
public int personId;
public List<int> contacts;
}
Using the following map with AutoMapper
Mapper.Map<PersonDto, Person>();
I'm using AutoMapper to get the DTO, which isn't a problem.
I'm parsing the DTO back to the Entity, to update fields in the Entity for a save operation and I'm not really interested in the list of contacts anymore. Automapper throws an exception with this as it doesn't like mapping the list of int's to a list of objects.
any suggestions or better ways to do this please.
Edit
solution used is
Mapper.CreateMap<PersonDto, Person>()
.ForMember(x => x.contacts, y => y.Ignore());
Can you use the ignore method in configuration?
http://automapper.codeplex.com/wikipage?title=Configuration%20Validation
opt => opt.Ignore()
But. Do you really need to update the entity just to save? Why don't you send a command which contains the changed data.
AutoMapper as you may know, looks for properties in objects with the same name & type, therefore you will most likely need to alter your DTO or Entity so they match.
I'm building CMS like application.
For example my BlogPost page contains several widget areas. Each widget hosts a serie of "related" blog posts.
All my views are pure presentational, i build urls, convert datetime and ints into strings in my service layer. I find this approach easier to maintain, since views have zer0 logic. All logic is consolidated into AutoMapper's resolvers, converters and custom transformation logic.
So lets come closer to the problem at hand.
To create Url i need 2 parameters: BlogId and BlogSlug, my urls look like b/{id}/{slug}.html
Im quite happy with that.
In my CSM i use so called "source models", a model that is not view model, but an intermediate representation of it. Why do i have to resort to such wicked solutions ?
Well, lets take a look how a typical data retrieval code can look like in my project:
.Select(x =>
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug,
// Here is the trap, LINQ provider will throw an exception, since he doesn't know how to translate function into expression
BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.Blog.Id, slug = x.Blog.Slug })
}
So thats not an option.
Luckily, we can do this
.Select(x => new
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => new
{
// This works
BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.BlogId, slug = x.BlogSlug })
}
Copy paste this stuff into each and every action method that renders different "intresting blog" parts (they have different visual representation as well cant use same view model) ? Not a good way, so i came up with a solution.
I created "source model", so the code will be
.Select(x => new BlogPostSourceViewModel
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => x.ToBlogPostViewModel()) // Extension method { return Mapper.Map<>() }
.ToList();
This surely looks better, but i have many different models like BlogPostSourceViewModel, BlogAuthorSourceViewModel, BlogCommentSourceViewModel. They all need this link building logic.
Ok, i extract the needed source data (BlogId, BlogSlug) into an interface
BlogPostSourceViewModel : IBlogPostUrl
BlogAuthorSourceViewModel: IBlogPostUrl
BlogCommentSourceViewModel : IBlogPostUrl
Then i define the mappings
Mapper.CreateMap<BlogPostSourceViewModel, BlogPostViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogAuthorSourceViewModel, BlogAuthorViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogCommentSourceViewModel, BlogCommentViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Resolver:
BlogPostUrlResolver : ValueResolver<IBlogPostUrl, String>
// Here goes the url building logic
As you see the more models i have that need blog url the more identical mappings i have to add. This is ok for now, but as project grows it will be painful.
Ideally i would want to have it like this:
Mapper.CreateMap<IBlogPostUrl, SomeOtherInterfaceWithBlogUrlAsString>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
but Automapper doesn't understand it. And i dont know how if there is other way to do it.
Any ideas ?
If I understand the question correctly...
In order to use AutoMapper mapping capabilities across all of your classes you can provide base class that will use Generics:
public abstract class Base<Entity, ViewModel>
where Entity : EntityObject
where ViewModel : BaseViewModel
{
// you will call this method from your operations class using base
public SomeViewModel GetData()
{
public Entity entityObject = db.Entity.SingleOrDefault();
public ViewModel yourViewModelName = base.Map(entityObject);
return yourViewModelName;
}
....
// this will be defined only once for each mapping direction
// ie. there will be multiple of these.
public static Entity Map(ViewModel typeViewModel)
{
try
{
Mapper.CreateMap<ViewModel, Entity>();
Entity t = Mapper.Map<ViewModel, Entity>(typeViewModel);
return t;
}
catch (Exception exc)
{
throw exc;
}
}
}
// this will be your class for database operations on specific Entity
// that inherits generic base, with its AutoMapping setup.
public class DataBaseOperationsClass : Base<SomeEntity, SomeViewModel>
{
public SomeViewModel Get()
{
return base.GetData();
}
}
Hope this helps !
I am using Fluent NHibernate 1.2 for NHibernate 3.1. I have a class:
public class Marks
{
public virtual int Id { get; set; }
public virtual IList<string> Answers { get; set; }
}
In the mapping for the Marks class, I have:
HasMany(m => m.Answers).Element("Value");
When the tables are created, an "Answers" table get created with the following columns:
Marks_id (FK, int, not null)
Value (nvarchar(255), null)
What I would like to do is have the Value be nvarchar(max). I'd prefer not doing this for every string in every class, just for this one class.
I have looked at these posts: first, second, third, but haven't found anything yet that helps.
Thanks in advance for any help you can offer. If you need additional information, please let me know.
Edit:
This is the code that resolves the issue:
HasMany(x => x.Answers).Element("Value", x => x.Columns.Single().Length = 4001);
You can force mapping string to longer column at each column level in mapping either by using CustomSqlType("nvarchar(max)") or, bit more universally, by setting Length(4001) (SQL Server magic number, above which it creates nvarchar(max) automatically).
To apply it automatically for all string columns in your entities, you can write your own FluentNHibernate convention:
public class LongStringConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(string));
}
public void Apply(IPropertyInstance instance)
{
instance.Length(4001);
}
}
And register it in mapping container i.e. like that:
Fluently.Configure()
.Mappings(...)
.Conventions.Add<LongStringConvention>()
To apply it for collection of strings, you can use custom element mappings:
HasMany(x => x.Answers).Element("Value", x => x.Columns.Single().Length = 4001);
Came across this issue myself and the above answers were most helpful in pointing me in the right direction...but I'm using a slightly newer version of FluentNH.
For Fluent NHibernate 1.3 and NH 3.3.1, the correct code is:
HasMany(x => x.Answers).Element("Value", x => x.Length(4001));
The answers above only work for older version of nhibernate.
If you try HasMany(x => x.Answers).Element("Value", x => x.Length(4001)); you will get the following:
Error Property or indexer
'FluentNHibernate.MappingModel.ColumnMapping.Length' cannot be
assigned to -- it is read only
The correct way to do this now is (NHibernate 4.0.2.4, FluentNHibernate 2.0.1.0):
HasMany(m => m.Answers).Element("Value", e => e.Length(4001))