AutoMapper sets related entities to null despite using opt => opt.Ignore() - c#

I am trying to map two objects of the same type through AutoMapper (most recent version installed through NuGet). My code:
public class School
{
public int Id {get; set;}
public string Name{get; set;}
public User CreatedBy {{get; set;}
}
public class User
{
public int Id {get; set;}
public string Code {get; set;}
}
I have EF 6 with MVC. In edit, a copy of School is given to the view and when it is submitted, I am trying to map it to the one in the database so that I can save it. I don't want to change the User in School while mapping. So my code is:
In Edit Controller Action
//...
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
Mapper.CreateMap<School, School>().ForMember(r => r.User, opt => opt.Ignore());
//viewModel.School is an object of School type.
school = Mapper.Map<School>(viewModel.School);
//Here school.User is set to null!
Amazed to see school.User is null. I thought I have given express exclusion to the mapper to ignore the destination value. I presume in such cases, it should retain the source property. I have also tried UseDestinationValue() instead. But no luck. What's causing this error? I sometimes wonder if any other better approach is available for EF-specific object mapping.

This is happening because you are creating a new instance of school rather than updating the one you've pulled from the database.
Instead of:
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
school = Mapper.Map<School>(viewModel.School);
Do:
var school = db.Schools.FirstOrDefault(s => s.Id == viewModel.SchoolId);
Mapper.Map(viewModel.School, school);
Rather than creating a new instance, this will update the one you already have.
As an aside, you should not be creating maps in your action methods - this should be run in your application initialisation, as it does not need to run on each method.

Related

Entity Framework with an Intersect table and correspond Linq query

I am new to Entity Framework and Linq. I am using .Net Core and EF Core 5. I like it so far but have hit a couple of issues that I am struggling with. This is the one I am really confused about and not understanding.
I have some products represented in a class. I have customers that buy these products in another class. Each customer may call one of my products a different name within their business so I need to allow them to define an alias that they use for my product.
Here are my two parent classes (Products & Customers)
public class Product
{
public int Id {get; set;}
public Guid IdGuid {get; set;}
public string Name {get; set;}
}
public class Customer
{
public int Id {get; set;}
public Guid IdGuid {get; set;}
public string Name {get; set;}
}
In these classes I have an Id column that is used by the Database for referential integrity. I do not pass this Id back to the user. Instead whenever the user gets one of these objects they get the Guid returned so they can uniquely identify the row in the DB. The alias class that joins these two tables is as follows:
public class ProductCustomerAlias
{
public Product Product {get; set;}
public Customer Customer {get; set;}
public string Alias {get; set;}
}
Since I need the database table to use a complex key consisting of the Product Id and the Customer Id I have to override the OnModelCreating method of the context object:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductCustomerAlias>()
.HasKey("ProductId", "CustomerId");
}
'''
This ends up creating a Database table that has the following structure (in Oracle)
'''
CREATE TABLE "RTS"."ProductCustomerAlias"
(
"ProductId" NUMBER(10,0),
"CustomerId" NUMBER(10,0),
"Alias" NVARCHAR2(100)
)
So far so good. I now have an intersect table to store Alias' in that has a primary key of ProductId and CustomerId both being the key integer values from the Products and Customers within the DB context.
So my next step is to start creating the Repo class that retrieves data from these objects. Keep in mind that the end user that submits the request can only pass me the Product.IdGuid and Customer.IdGuid because that is all they ever have. In my Repo I have the following code:
public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(Guid pProductId, Guid pCustomerId)
{
var alias = await (from ali in _context.ProductCustomerAlias
join prod in _context.Products on ali.Product.Id equals prod.Id
join cust in _context.Customers on ali.Customer.Id equals cust.Id
where cust.IdGuid == pCustomerId && prod.IdGuid == pProductId
select new {Product = prod,
Customer = cust,
Alias = ali.Alias}
).FirstOrDefaultAsync();
return (IEnumerable<ProductCustomerAlias>)alias;
}
My problem is that it is giving me the following error:
Cannot convert type '<anonymous type: Models.Product Product, Models.Customer Customer, string Alias>' to 'System.Collections.Generic.IEnumerable<Models.ProductCustomerAlias>'
Please don't tell me that the error is telling me exactly what is wrong. I am sure it is but if I understood where I was screwing up I would not be wasting my time typing out this ridiculously long explanation. So how can I cast the results from my Linq query to the specified type? Is there something else I am doing wrong? Any help would be greatly appreciated.
Answering your concrete questions.
So how can I cast the results from my Linq query to the specified type?
You can't, because the LINQ query result is anonymous type which is not compatible with the desired result type (concrete entity type), thus cannot be cast to it.
Is there something else I am doing wrong?
Sorry to say that, but basically everything is wrong.
Select is not needed because the desired result type is the exact type of the entity being queried. i.e. ali variable here
from ali in _context.ProductCustomerAlias
is exactly what you need as a result (after applying the filter and limiting operators)
Manual joins are also not needed, because they are provided automatically by navigation properties, i.e. here
join prod in _context.Products on ali.Product.Id equals prod.Id
the prod is exactly the same thing as ali.Product
attempt to cast single object to enumerable is wrong
return (IEnumerable<ProductCustomerAlias>)alias
Even if the alias variable was of correct type, this will fail because it is single object rather than a collection.
So, the solution is quite simple - use the corresponding DbSet, apply filter (Where), limit the result to zero or one (FirstOrDefault{Async}) and you are done.
With one small detail. Since you are querying and returning a full entity, its navigation properties (like Product and Customer) are considered to be a related data, and are not populated (loaded) automatically by EF Core. You have to explicitly opt-in for that, which is called eager loading and explained in the Loading Related Data section of the official EF Core documentation (I would also recommend familiarizing with navigation properties and the whole Relationships concept). With simple words, this requires usage of specifically provided EF Core extension methods called Include and ThenInclude.
With all that being said, the solution is something like this:
public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(
Guid pProductId, Guid pCustomerId)
{
return await _context.ProductCustomerAlias
.Include(a => a.Product)
.Include(a => a.Customer)
.Where(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId)
.FirstOrDefaultAsync();
}
You can even replace the last two lines with single call to the predicate overload of FirstOrDefaultAsync, but that's not essential since it is just a shortcut for the above
return await _context.ProductCustomerAlias
.Include(a => a.Product)
.Include(a => a.Customer)
.FirstOrDefaultAsync(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId);

Single Element Using Lookup operator

I am stuck in an issue with MongoDB. I am using C# driver for MongoDB.
So basically I am trying to join Categories document with Users document. Each category document has one property:
public Guid UserId { get; set; }
This property is an ObjectId behind the scenes. I have another property:
public UserDoc User { get; set; }
Now I am trying to fill this User property with all the user details based on the UserId property. This is the code I am trying to achieve this:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDoc>(
usersCollection,
x => x.UserId,
x => x.Id,
x => x.User)
As expected, 'Lookup' is expecting an array of User documents but I have a property referencing a single user object and thus, it throws an error:
An error occurred while deserializing the User property of class TatSat.API.Documents.CategoryDoc: Expected a nested document representing the serialized form of a TatSat.API.Documents.UserDoc value, but found a value of type Array instead.
Can someone help me with this? I am new to Mongo so this is a bit of a pain for me. Kindly note that I am looking for a strongly typed solution and don't want to mess with BsonDocuments if that can be avoided.
Thanks in advance.
So I finally figured out in some ways. Basically I decided to have another class:
public class CategoryDocWithTemporaryData : CategoryDoc
{
public UserDoc[] Users { get; set; }
public static Expression<Func<CategoryDocWithTemporaryData, CategoryDoc>> ToCategoryDoc => c =>
new CategoryDoc
{
Id = c.Id,
//other properties
User = c.Users.First()
};
}
Then I use the lookup as:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDocWithTemporaryData>(
usersCollection,
c => c.UserId,
c => c.Id,
c => c.Users)
.Project(CategoryDocWithTemporaryData.ToCategoryDoc)
This, however needs me to use additional projection where I have to manually select all properties which is something I wanted to avoid. But I think I can live with it until I come across a better approach.
Lookup will always return an array, even if it only has 1 element. If you need just the first (or only) document in that array, use a project/set/addFields stage with arrayElemAt.

While returning a DTO which is mapped using automapper, how do I assign it a list of it's child class type?

I have an entity
Topics having these properties
1. public string Name {get; set;}
2. public string Location {get; set;}
3. public string Identity {get; set;}
4. public TopicsMapped TopicsMapped {get; set;}
Also, I have one DTO that is TopicsDTO and it has properties
1. public string Name {get; set;}
2. public string Location {get; set;}
3. public string Identity {get; set;}
4. public TopicsMapped TopicsMapped {get; set;}
so in Automapper.config I have created a mapping of these 2.
roughly,
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Topics, TopicsDTO>();
});
now in webapi method, I am using linq to sql and I have written something like this
var query= (from t in topics select t).tolist();
Topics t= new topics();
var mapper = new Mapper(config);
var topicsDTO = mapper.Map<List<TopicsDTO>>(query);
return OK(topicsDTO) //since the webapi method returning IHttpActionResult.
But my problem is that I have to assign a list of TopicsMapped type and return in OK as a part of TOPICs DTO. I tried taking join of both types and assign to query but it threw error that mapping configuration is not properly configured.
How do I do this?
Defining a DTO for your web interface is good, but the purpose of implementing a DTO is to provide a model specific to what the consumer needs and to avoid passing more data that is needed, or data/schema information that the consumer has no right to see.
Assuming TopicsMapped is a class containing some fields, the relationship between Topic & TopicsMapped is a one-to-one. Typically in a one-to-one relationship you might want to provide some relevant fields from the related entity. A general rule to follow when mapping DTOs or ViewModels is don't mix DTOs (view models) with entities. (data models) This can lead to problems because if TopicsMapped has references back to Topic or other entities/tables, putting that entity into your DTO could see lazy load calls or exceptions when serializing your DTO. Either create a DTO graph for related entities where needed, or flatten the DTO model to represent the data you care about.
For example, if the TopicsMapped has a field called "Name" that we want to include for our web API, we don't need the entire TopicsMapped model, just the field. So we can flatten that down in the DTO:
[Serializable]
public class TopicDTO
{
public int TopicId { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public string Identity { get; set; }
public string MappedName { get; set; } // <- Will map to TopicsMapped.Name
}
Then with the automapper configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Topics, TopicsDTO>()
.ForMember(x => x.MappedName, opt => opt.MapFrom(src => src.TopicsMapped.Name));
};
By default Automapper can use convention to work out mappings for destination fields from the source object, and if you follow those conventions in your DTO it is possible to avoid an explicit mapping, but I honestly prefer explicit mapping to avoid the guesswork and bugs if property names get changed. It also helps ensure usage counts for properties are properly accounted for. Linq2SQL should provide access to IQueryable, so to properly leverage projection /w Automapper you will want to use ProjectTo rather than Map:
var topicDTOs = Context.Topics
.ProjectTo<TopicDTO>(config)
.ToList();
This will involve adding a using clause for "Automapper.QueryableExtensions". The difference between ProjectTo and Map is that with Map, the SQL generation happens with your ToList call. Map would have to resort to lazy loading the data from the TopicMapping class or you would have to set it up to eager-load that data. In both cases this would essentially include all fields from TopicMapping even though we only want 1 field in this example. ProjectTo on the other hand lets Automapper in on the SQL generation. The resulting query would only return the fields that the DTO needs from the Topic and TopicMapping tables, nothing more. No need to eager load related entities, and it produces much more streamlined queries.
Disclaimer: From what I have read, IQueryable and Automapper's ProjectTo should be supported with Linq2SQL. If you encounter issues I would strongly recommend considering using Entity Framework rather than Linq2SQL as it has full support for projection, eager, and lazy loading.

EntityFramework code-first inheritance with eager include relationship on derived class

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.

LINQ Group By if an object has x number of properties, then select on each group

I have some experience with LINQ but writing this query is proving to be a bit above my head.
Excuse my pseudo-code...
class Person{
Collection<Communications> {get;set}
}
class Communication{
Collection<PersonSender> {get;set;}
Collection<BuildingSender> {get;set;}
}
class PersonSender{
Collection<Posts> {get;set}
}
class BuildingSender{
Collection<Posts> {get;set}
}
What I want to accomplish: Group the Communication collection on whether it contains an instance of PersonSender or BuildingSender when those instances have a Post instance themselves.
Then I want to perform a select query on each group of Collection objects so I can return an IEnumerable collection of another object Package that is created in the select statement using each Collection's properties. The key here is that I need to perform a seperate select statement on each group returned
This is what I've got for the actual query so far
m.Communications.GroupBy(x =>
new {fromMember = (x.PersonSender.Any() && x.Posts.Any()),
fromBuilding = (x.BuildingSender.Any() && x.Posts.Any())})
.Select(u => new Package(u.PersonSender.First().Member,u.Posts.First()));
However I'm pretty sure this doesn't compile and it doesn't offer me the multiple select statements I need.
Is GroupBy the right way to go about this? Is it even possible?
UPDATE: Per #Hogan I have been able to hack together a work solution. Let me try to clear up what I was trying to do though, my original question wasn't very clear...
This code is part of the class PackageFactory . Each method in this class can be invoked by a Controller in my application asking for a set of Package objects. The Package accepts several types of IEntity objects as parameters and wraps the content that is associated with the relationships the IEntity objects have into an interface that any other controller displaying information on my application can read. TLDR Package is a glorified Adapter pattern design object.
Each method in PackageFactory has the job of querying the Communication repository, finding the relevant Communication objects with the right set of properties, and then passing the subset of objects(that are properties of the Communication object) to the a new Package instance to be wrapped before returning the whole set of Package objects to the controller so they can be rendered on a page.
In the case of the method I am writing for this question the user m has a collection of Communication objects where each Communication comes from an IEntity object(either PersonSender or BuildingSender) that was directed at the user m . My query was an attempt to segregate the Communication objects into two sets where one contains all Communication where PeronSender exists and one where BuildingSender exists. This way my method knows which group gets passed to their respective Package type.
My logic for trying to use GroupBy was that I would rather make the query as generic as possible so that I can expand to more sets later AND/OR increase the performance of the method by not having to call many seperate queries and then join them all. However it seems like specifying distinct select queries on each group is not a possibility.
#Hogan 's answer is close to what I want to be able to do.
Hogan's Answer
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));
Modified Answer
This is what works:
return m.Communications.Where(x => x.SendingPerson.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.SendingPerson.First().Member,m,u.Posts.First()))
.Union(m.Communications.Where(x=> x.BuildingSender.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.BuildingSender.First().Building,m,u.Posts.First())));
Not exactly the same -- My head was a bit foggy when I wrote this question yesterday.
I think the key is the SelectMany not GroupBy -- This will "flatten" a sub-lists. As I've shown below:
With
class Person{
Collection<Communications> comm {get; set;}
}
class Communication{
Collection<PersonSender> person {get; set;}
Collection<BuildingSender> building {get; set;}
}
class PersonSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
class BuildingSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
given that m is a person:
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));

Categories