Automapper with linq how? - linq

Ok, I'm really struggling with finding a good example of what I need to do. So, I'll ask here.
Let's say I have a entity class (EF) named Customer and a corresponding view-model class named CustomerViewModel.
Using AutoMapper, I have created the following mappings:
Mapper.CreateMap<CustomerViewModel, Customer>();
Mapper.CreateMap<Customer, CustomerViewModel>();
How would I modify the following code to make use of this mapping?
public static List<CustomerViewModel> GetCustomers()
{
using (var context = new GCSBaseEntities())
{
var query = from c in context.Customers
select new CustomerViewModel
{
CompanyName = c.CompanyName,
Id = c.Id,
EmailAddress = c.EmailAddress,
FirstName = c.FirstName,
LastName = c.LastName,
MiddleName = c.MiddleName,
ModifiedDate = c.ModifiedDate,
Phone = c.Phone,
SalesPerson = c.SalesPerson,
Suffix = c.Suffix,
Title = c.Title,
FullName = c.FirstName + " " + c.LastName
};
return query.ToList();
}
}
Thanks in advance.

When you register your mappings, you must provide any complex mapping operations that have to occur. In your case, I believe all your properties match up, except for FullName = c.FirstName + " " + c.LastName. Here's how your Customer-to-CustomerViewModel mapping should look:
Mapper.CreateMap<Customer, CustomerViewModel>()
.ForMember(custVm => custVm.FullName,
mapper => mapper.MapFrom(cust => cust.FirstName + " " + cust.LastName));
You'll have to figure out how to shove the FullName prop from the ViewModel back into the FirstName & LastName fields on the EF class, though. But when you decide how to implement it, follow the pattern from above for the other mapping.
Your query can now be MUUUCH smaller:
using (var context = new GCSBaseEntities())
{
return from c in context.Customers
select Mapper.Map<CustomerViewModel>(c);
}

Figured it out. In order to avoid the aforementioned error, you have to Add the call the .AsEnumerable() after Customers like so:
return from c in context.Customers.AsEnumerable()
select Mapper.Map<CustomerViewModel>(c);
I got this from this thread: LINQ and AutoMapper

Related

Translating LINQ query into CRM QueryExpression while using 'select new'

I've got the following container class:
public class AssetAndItsDepositsContainer
{
public jp_asset Asset { get; set; }
public jp_deposit Deposit { get; set; }
}
Is there a way to take the following LINQ query:
from a in serviceContext.jp_assetSet
join d in serviceContext.jp_depositsSet on a.Id equals d.jp_assetid.Id
where a.statecode == jp_assetState.Active &&
a.jp_isonhold = true
select new AssetAndItsDepositsContainer()
{
Asset = a,
Deposit = d
})
.ToList();
And "translate" it by using QueryExpression?
This is what I came up with so far, but I don't know how to mimic the select new expression:
QueryExpression query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet = new ColumnSet(true);
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
// Now what?
var res = service.RetrieveMultiple(query).Entities; // gets only jp_assets
You can't access an entire LinkEntity, you can only access its attributes as AliasedValue properties on the main Entity itself.
I would simply retrieve the Id of the second record you're looking for as part of an EntityReference and then execute a Retrieve.
You can actually construct valid Entity objects from linked entities. The only requirement is that the primary key of the linked entity must be available in the result set.
So, your code snippet could be extended like this:
var query = new QueryExpression(jp_asset.EntityLogicalName);
query.ColumnSet.AllColumns = true;
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, (int)jp_assetState.Active);
query.Criteria.AddCondition("jp_isonhold", ConditionOperator.Equal, true);
LinkEntity link = query.AddLink(jp_deposit.EntityLogicalName, "Id", "jp_assetid", JoinOperator.Inner);
link.EntityAlias = "d";
link.Columns.AddColumns("jp_depositid", "jp_name");
IEnumerable<Entity> deposits = Service.RetrieveMultiple(query).Entities
.Select(e => new Entity("jp_deposit")
{
Id = (Guid)e.GetAttributeValue<AliasedValue>("d.jp_depositid").Value,
["jp_name"] = e.GetAttributeValue<AliasedValue>("d.jp_name")?.Value
});
Note
The example above will only work reliable for inner joins. In the result set the primary key (ID) for the linked entity will always be available. Therefore it is safe to get its value using the .Value syntax. All other attribute values can be retrieved using ?.Value.
For left joins you would need to filter the result set first before performing the Select.

Linq get all fields from select along with dynamic column

I have such Linq, I would like to get all records from Orders table and also add new dynamic field. The code below do not work. What is correct syntax?
user.dcOrders.Select(p =>p, new { FullName = p.FirstName + " " + p.LastName })
You'll need to list out all the columns.
Alternatively you could add a new property to whatever class dcOrders is. It should be specified as a partial class so you can add the FullName property to a new file so it doesn't get overwritten when the .designer.cs file is regenerated.
So something like
public partial class Orders
{
public string FullName { get { return this.FirstName + " " + this.LastName; } }
}
Be sure to add this to a separate file not the .designer.cs file. (I'm assuming LINQ to SQL here).
Then you don't need to do any special select, because FullName will already exist as a property on the object. You can just use it directly.
Try this:
var names = user.dcOrders.Select(p => new {
User = p,
FullName = p.FirstName + " " + p.LastName
});

Entity Framework - LINQ selection in POCO generic List property

I'm having some issues setting a generic list property of a POCO object when from an EF context. For instance I have a very simple object that contains the following:
public class foo
{
public string fullName;
public Entity entity;
public List<SalesEvent> eventList;
}
My code to populate this object from looks something like this:
.Select(x => new foo()
{
fullName = x.vchFirstName + " " + x.vchLastName,
entity = new EntityVo()
{
address1 = x.vchAddress1,
entityId = x.iEntityId,
emailAddress = x.vchEmailAddress,
firstName = x.vchFirstName,
lastName = x.vchLastName,
city = x.vchCity,
state = x.chState,
workNumber = x.vchWorkNumber,
mobileNumber = x.vchMobileNumber,
siteId = x.iSiteId
}
eventList = _context.Events
.Where(e => e.iEntityId == x.iEntityId
&& e.iStatusId >= eventStatusMin
&& e.iStatusId <= eventStatusMax)
.Select(e => new List<SalesEventMatchVo>
{
new SalesEventMatchVo()
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
}
}).FirstOrDefault()
}).ToArray();
This issue I'm having is that I'm unable to populate the eventList property with all of the events, it's only grabbing the first record(which makes sense looking at the code). I just cant seem to figure out to populate a the entire list.
Is there a reason simply removing the FirstOrDefault at the end isn't the solution here? I feel like I might be misunderstanding something.
EDIT:
I think I see what you are trying to do. The issue is that you are creating a list in the select statement, when the select statement works only over one thing at a time. It is basically mapping an input type to a new output type.
Try something like this instead:
eventList = _context.Events.Where(e => e.iEntityId == x.iEntityId && //FILTER EVENTS
e.iStatusId >= eventStatusMin &&
e.iStatusId <= eventStatusMax)
.Select(e => new SalesEventMatchVo() //MAP TO SALESEVENT
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
})
.ToList() //CONVERT TO LIST
As a side note, unless you actually need a List for some reason, I would store foo.eventList as IEnumerable<SalesEvent> instead. This allows you to skip the List conversion at the end, and in some scenarios enables neat tricks like delayed and/or partial execution.
Also, I'm not sure what the point of your .Select(q=>q) statements are in several lines of the SalesEventMatchVo initializer, but I'm pretty sure you can chop them out. If nothing else, you should Select after Where, as Where can reduce the work performed by all following statements.

IN and NOT IN with Linq to Entities (EF4.0)

This has been ruining my life for a few days now, time to ask...
I am using Entity Framework 4.0 for my app.
A Location (such as a house or office) has one or more facilities (like a bathroom, bedroom, snooker table etc..)
I want to display a checkbox list on the location page, with a checkbox list of facilities, with the ones checked that the location currently has.
My View Model for the facilities goes like this...
public class FacilityViewItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
So when im passing the Location View Model to the UI, i want to pass a List<T> of facilities where T is of type FacilityViewItem.
To get the facilities that the location already has is simple - i make a query using Location.Facilities which returns an EntityCollection where T is of type Facility. This is because Facilities is a navigation property....
var facs = from f in location.Facilities
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name,
Checked = true
};
So here is where my problem lies - i want the rest of the facilities, the ones that the Location does not have.
I have tried using Except() and Any() and Contains() but i get the same error.
Examples of queries that do not work...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilities.Contains(f)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
var restOfFacilities = ctx.Facilities.Except(facilitiesThatLocationHas);
var notFacs = from e in ctx.Facilities
where !hasFacilities.Any(m => m.FacilityId == e.FacilityId)
select new FacilityViewItem()
{
Id = e.FacilityId,
Name = e.Name
};
And the error i get with every implementation...
System.NotSupportedException was unhandled
Message=Unable to create a constant value of type 'Chapter2ConsoleApp.Facility'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
What am i overlooking here?
ironically enough i solved it in a matter of hours after i posted the question on here, after days of suffering.
The error is basically saying 'i dont know how to calculate what items are not included by comparing strongly typed objects. Give me a list of Ints or some simple types, and i can take care of it'.
So, first you need to get a list of the primary keys, then use that in the contains clause...
//get the primary key ids...
var hasFacilityIds = from f in hasFacilities
select f.FacilityId;
//now use them in the contains clause...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilityIds.Contains(f.FacilityId)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
The first query seems fine, but you need to compare the Ids:
var restOfFacilities = from f in ctx.Facilities
where !facs.Select(fac => fac.Id).Contains(f.Id)
select f;
I wanna see what's hasFacilities, anyway, as L2E shows, "Only primitive types ('such as Int32, String, and Guid') are supported in this context", so I suppose you must retrieve first the data and put into a collection of FacilityViewItem.
var restOfFacilities = ctx
.Facilities
.Where(f => !hasFacilities.Contains(f))
.Select(f => new { f.FacilityId, f.Name })
.ToList()
.Select(f => new FacilityViewItem {
Id = f.FacilityId,
Name = f.Name
});
var notFacs = ctx
.Facilities
.Where(e => !hasFacilities.Any(m => m.FacilityId == e.FacilityId))
.Select(e => new { e.FacilityId, e.Name })
.ToList()
.Select(e => new FacilityViewItem {
Id = e.FacilityId,
Name = e.Name
});
hope it helps

Linq nested select new not working

I'm trying to get eager loading working with Subsonic, and it's been returning null for me.
In the method below, I'm trying to hydrate a domain model (UserModel) which contains another domain model (CompanyModel). However, with the code below, UserModel.Company is always null.
What am I missing here. Any help would be appreciated.
public IList<UserModel> GetUsers()
{
return (from u in SubsonicSqlServer.Users.All()
select new UserModel
{
UserId= u.UserId,
Company = (from c in u.Companies
select new CompanyModel
{
CompanyId = c.CompanyId,
CompanyName = c.CompanyName
}).SingleOrDefault(),
FirstName = u.FirstName,
LastName = u.LastName,
BirthDate = u.BirthDate
}).ToList();
}
Update (08/11/09):
More toying around with the code, I found out that setting CompanyId in the following example doesn't work either. I initially thought this was an issue with Subsonic, but if the code below doesn't work, I'm guessing it has something to do with my Linq statement. Any ideas?
public IList<UserModel> GetUsers()
{
return (from u in SubsonicSqlServer.Users.All()
select new UserModel
{
UserId= u.UserId,
CompanyId = Guid.NewGuid(),
FirstName = u.FirstName,
LastName = u.LastName,
BirthDate = u.BirthDate
}).ToList();
}
Update (11/17/2009):
Still haven't found a solution. But we are switching to nHibernate (not because of this issue).
"UserModel.Company is always null."
since you are setting this with an expression that ends with .SingleOrDefault(), I'm going to suggest that the query isn't returning a single item. Start investigating there. If you are expecting exactly one item in u.Companies, change to .Single() and force an early failure.
You can do the .Single() before creating the new CompanyModel object, I think.
As for style, I like the query comprehension syntax ("from x in y select") but find it awkward when combined with traditional dot-notation syntax. It's just hard to read. (LINQ - Fluent and Query Expression - Is there any benefit(s) of one over other?).
Consider using let in the query comprehension to make it clearer.
Also, since a query already returns an IEnumerable<T>, and calling ToList() forces all items to be realized, I would modify my method to return IEnumerable<T> if possible.
So, in your case, I would refactor the first to say:
public IEnumerable<User> GetUsers()
{
return from u in SubsonicSqlServer.Users.All()
let c = u.Companies.Single()
select new UserModel
{
UserId = u.UserId,
Company = new CompanyModel
{
CompanyId = c.CompanyId,
CompanyName = c.CompanyName
},
FirstName = e.FirstName,
LastName = e.LastName,
BirthDate = e.BirthDate
};
}
If it makes sense in your object model, you could modify User to have a constructor that takes whatever type u is, and it gets even simpler:
return from u in SubsonicSqlServer.Users.All()
select new UserModel (u);
or even
return SubsonicSqlServer.Users.All().Select(u => new UserModel (u));
Two things
You're returning a List<UserModel> when your method's signature line says IList<User> does UserModel inherit from User?
Am I missing something, where does e come from?
FirstName = e.FirstName,
LastName = e.LastName,
BirthDate = e.BirthDate Blockquote
Please check out my fork # github (http://github.com/funky81/SubSonic-3.0/commit/aa7a9c1b564b2667db7fbd41e09ab72f5d58dcdb) for this solution, actually there's a problem when subsonic try to project new type class, so there's nothin wrong with your code actually :D

Resources