Linq to SQL construct a custom object externally - join from another object - linq

Continued from this solution (thanks Daniel Hilgarth)
return db.Tags.Select(ConstructTagItem());
And the method:
private Expression<Func<Tag, TagItem>> ConstructTagItem()
{
return a => new TagItem {ID = a.Id Name = a.Name };
}
Additional question, how do i use it in this scenario then:
return (from c in db.News_Attributes
select new NewsTagItem
{
NewsID = c.News_Id,
TagID = c.Tag_Id,
Content = c.Content,
Attribute = new TagItem
{
ID = c.Tag.Id,
Name = c.Tag.Name
}
});
I want to reuse the method from the other answer:
private Expression<Func<Tag, TagItem>> ConstructTagItem
{
get { return a => new TagItem {ID = a.Id Name = a.Name }; }
}
To construct something like this:
return (from c in db.News_Attributes
select new NewsTagItem
{
NewsID = c.News_Id,
TagID = c.Tag_Id,
Content = c.Content,
Attribute = ConstructTagItem // TODO: need some way to tell the method that it should use c.Tag
});
I want to use the same construction of my TagItem multiple places. This will make it easier if the object changes, and save lines.
I guess that I somehow have to define that it is c.Tag into ConstructTagItem(), but I really don't know much about expressions yet. So i hope that someone is able to help?

I'm not sure if I have a full handle on what you're trying to do. What does "use it in this scenario" mean? Can you mimic your previous technique with something like this in order to encapsulate creating a NewsTagItem, or is it something else you're trying to achieve?
private Expression<Func<News_Attribute, NewsTagItem>> ConstructNewsTagItem()
{
return c => new NewsTagItem
{
NewsID = c.News_Id,
Name = a.Name
TagID = c.Tag_Id,
Content = c.Content,
Attribute = new TagItem
{
ID = c.Tag.Id,
Name = c.Tag.Name
}
}
});
db.News_Attributes.Select(ConstructNewsTagItem());
UPDATE:
OK, we can't directly re-use your ConstructTagItem() because it returns an expression containing a function. What you need is a MemberInitExpression. It's a little tricky to create by hand, but we can use a trick whereby we create the expression we desire wrapped with a thunk, so that it isn't evaluated, and then grab the body of the thunk to get the expression. See the snippet below:
private Expression GenerateNewTagItem(TagItem c)
{
Expression<Func<TagItem>> expr = () => new TagItem { ID = c.ID, Name = c.Name };
return expr.Body;
}
With this function, we can now do pretty much exactly what you want:
return (from c in db.News_Attributes
select new NewsTagItem
{
NewsID = c.News_Id,
TagID = c.Tag_Id,
Content = c.Content,
Attribute = GenerateNewTagItem(c)
});
Pretty neat right?

Related

IQueryable.Union/Concat in .net core 3

I want to add a dummy member to an IQueryable and came up with this solution:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll(); //DbSet<Geography>
var dummyGeographies = new Geography[] { new Geography { Id = -1, Name = "All" } }.AsQueryable();
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList(); //throws runtime exc
But it throws the following exception:
Processing of the LINQ expression 'DbSet
.Union(EnumerableQuery { Geography, })' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
How could I make it work?!
you can only union on data structure which are the same
IQueryable is only applicable if the query expression not been been expressed (ToList) before its run against db and you want the expression modifiable . aka nothing which which is not going to db as a query needs to be IQueryable (simple explanation better to research and understand this yourself)
List<Geography> geographies = _unitOfWork.GeographyRepository
.GetAll() //DbSet<Geography>
.Select(o => new Geography { Id = o.Id, Name = o.Name })
.ToList();
List<Geography> dummyGeographies = new List<Geography>() {
new Geography[] { new Geography { Id = -1, Name = "All" } }
};
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList();
I was able to achieve it with the following code:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = o.Id, Name = o.Name });
IQueryable<Geography> dummyGeographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = -1, Name = "All" });
var combinedGeographies = geographies.Union(dummyGeographies);

Convert if and foreach statement to select and where in linq

How would I go about changing my if statement and foreach to something cleaner in linq using select and where.
I've tried to make the if statement into a where clause and then use the select query as a replacement for the Foreach loop but that seem to have type issues and wasn't working.
{
StripeConfiguration.ApiKey = _appSettings.StripeSecretKey;
var profile = await _userManager.FindByIdAsync(customerServiceID);
var stripeId = profile.StripeAccountId;
if (stripeId == null)
throw new ArgumentException("No associated Stripe account found.");
List<PaymentMethodDto> result = new List<PaymentMethodDto>();
var options = new PaymentMethodListOptions
{
Customer = stripeId,
Type = "card",
};
var service = new PaymentMethodService();
var payments = await service.ListAsync(options);
if (payments != null && payments.Data?.Count > 0)
{
payments.Data.ForEach((x) =>
{
result.Add(
new PaymentMethodDto
{
Brand = x.Card.Brand,
LastDigits = x.Card.Last4,
StripeToken = x.Id,
CustomerID = x.CustomerId
});
});
}
return result;
}
Just do a regular Select.
List<PaymentMethodDto> result = payments.Data.Select(x => new PaymentMethodDto
{
Brand = x.Card.Brand,
LastDigits = x.Card.Last4,
StripeToken = x.Id,
CustomerID = x.CustomerId
})
.ToList();
If payments.Data has nothing in it, this will give you an empty list, which is what you want.
If payments is null, you'll get an exception, which I think if you think about it really hard is probably what you really want in that case too. Why would .ListAsync() yield a null value?

list inside another list select

what is the correct syntax to write the second list? bookid and other fields are not recognizing
var bookssublist = from bookdetails in bookslist
join bookcategories in _context.BookCategories
on bookdetails.BookId equals bookcategories.BookId
where bookcategories.CategoryId==CategoryId
select new BookBasicInfo {
count = bookcount,
BookInfo = new List<BookInfo>()
{
BookId = bookdetails.BookId,
BookTitle = bookdetails.Title,
Images = bookdetails.ThumbnailImagePath,
PublishDate = bookdetails.PublishedDate,
AuthorList = bookdetails.BookAuthors.Select(q => q.Author.Author1).ToList(),
CategoryList =bookdetails.BookCategories.Select(q=>q.Category.CategoryName).ToList(),
}
};
You are using collection initializer in a wrong way. Actually, you forgot to pass an object of type BookInfo to the initializer.
BookInfo = new List<BookInfo>()
{
new BookInfo()
{
BookId = bookdetails.BookId,
BookTitle = bookdetails.Title,
Images = bookdetails.ThumbnailImagePath,
PublishDate = bookdetails.PublishedDate,
AuthorList = bookdetails.BookAuthors.Select(q => q.Author.Author1).ToList(),
CategoryList =bookdetails.BookCategories.Select(q=>q.Category.CategoryName).ToList()
}
}

EF single entity problem

I need to return a single instance of my viewmodel class from my repository in order to feed this into a strongly-typed view
In my repository, this works fine for a collection of viewmodel instances:
IEnumerable<PAWeb.Domain.Entities.Section> ISectionsRepository.GetSectionsByArea(int AreaId)
{
var _sections = from s in DataContext.Sections where s.AreaId == AreaId orderby s.Ordinal ascending select s;
return _sections.Select(x => new PAWeb.Domain.Entities.Section()
{
SectionId = x.SectionId,
Title = x.Title,
UrlTitle = x.UrlTitle,
NavTitle = x.NavTitle,
AreaId = x.AreaId,
Ordinal = x.Ordinal
}
);
}
But when I attempt to obtain a single entity, like this:
public PAWeb.Domain.Entities.Section GetSection(int SectionId)
{
var _section = from s in DataContext.Sections where s.SectionId == SectionId select s;
return _section.Select(x => new PAWeb.Domain.Entities.Section()
{
SectionId = x.SectionId,
Title = x.Title,
UrlTitle = x.UrlTitle,
NavTitle = x.NavTitle,
AreaId = x.AreaId,
Ordinal = x.Ordinal
}
);
}
I get
Error 1 Cannot implicitly convert type
'System.Linq.IQueryable<PAWeb.Domain.Entities.Section>' to
'PAWeb.Domain.Entities.Section'. An explicit conversion exists
(are you missing a cast?)"
This has got to be simple, but I'm new to c#, and I can't figure out the casting. I tried (PAWeb.Domain.Entities.Section) in various places, but no success. Can anyone help??
Your query is returning an IQueryable, which could have several items. For example, think of the difference between an Array or List of objects and a single object. It doesn't know how to convert the List to a single object, which one should it take? The first? The last?
You need to tell it specifically to only take one item.
e.g.
public PAWeb.Domain.Entities.Section GetSection(int SectionId)
{
var _section = from s in DataContext.Sections where s.SectionId == SectionId select s;
return _section.Select(x => new PAWeb.Domain.Entities.Section()
{
SectionId = x.SectionId,
Title = x.Title,
UrlTitle = x.UrlTitle,
NavTitle = x.NavTitle,
AreaId = x.AreaId,
Ordinal = x.Ordinal
}
).FirstOrDefault();
}
This will either return the first item, or null if there are no items that match your query. In your case that won't happen unless the table is empty since you don't have a where clause.

Dynamically Sorting with LINQ

I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.
I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.
Currently, I'm trying the following without any luck:
var results = myCollection.OrderBy(sortProperty);
However, I'm getting a message that says:
... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.
Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)
I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:
// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.Name == "OrderBy"
&& method.GetParameters().Length == 2)
.Single();
public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
this IEnumerable<TSource> source,
string propertyName)
{
// TODO: Lots of validation :)
PropertyInfo property = typeof(TSource).GetProperty(propertyName);
MethodInfo getter = property.GetGetMethod();
Type propType = property.PropertyType;
Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
Delegate func = Delegate.CreateDelegate(funcType, getter);
MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
typeof(TSource), propType);
return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
new object[] { source, func });
}
Test code:
string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };
foreach (string x in foo.OrderByProperty("Length"))
{
Console.WriteLine(x);
}
Output:
Jon
Tom
Holly
Robin
William
It even returns an IOrderedEnumerable<TSource> so you can chain ThenBy clauses on as normal :)
You need to build an Expression Tree and pass it to OrderBy.
It would look something like this:
var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
Expression.Property(param, sortProperty),
param
);
Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.
protected void sort_grd(object sender, GridViewSortEventArgs e)
{
if (Convert.ToBoolean(ViewState["order"]) == true)
{
ViewState["order"] = false;
}
else
{
ViewState["order"] = true;
}
ViewState["SortExp"] = e.SortExpression;
dataBind(Convert.ToBoolean(ViewState["order"]), e.SortExpression);
}
public void dataBind(bool ord, string SortExp)
{
var db = new DataClasses1DataContext(); //linq to sql class
var Name = from Ban in db.tbl_Names.AsEnumerable()
select new
{
First_Name = Ban.Banner_Name,
Last_Name = Ban.Banner_Project
};
if (ord)
{
Name = BannerName.OrderBy(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
else
{
Name = BannerName.OrderByDescending(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
grdSelectColumn.DataSource = Name ;
grdSelectColumn.DataBind();
}
you can do this with Linq
var results = from c in myCollection
orderby c.SortProperty
select c;
For dynamic sorting you could evaluate the string i.e. something like
List<MyObject> foo = new List<MyObject>();
string sortProperty = "LastName";
var result = foo.OrderBy(x =>
{
if (sortProperty == "LastName")
return x.LastName;
else
return x.FirstName;
});
For a more generic solution see this SO thread: Strongly typed dynamic Linq sorting
For this sort of dynamic work I've been using the Dynamic LINQ library which makes this sort of thing easy:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx
You can copy paste the method I post in that answer, and change the signature/method names:
How to make the position of a LINQ Query SELECT variable
You can actually use your original line of code
var results = myCollection.OrderBy(sortProperty);
simply by using the System.Linq.Dynamic library.
If you get a compiler error (something like cannot convert from or does not contain a definition...) you may have to do it like this:
var results = myCollection.AsQueryable().OrderBy(sortProperty);
No need for any expression trees or data binding.
You will need to use reflection to get the PropertyInfo, and then use that to build an expression tree. Something like this:
var entityType = typeof(TEntity);
var prop = entityType.GetProperty(sortProperty);
var param = Expression.Parameter(entityType, "x");
var access = Expression.Lambda(Expression.MakeMemberAccess(param, prop), param);
var ordered = (IOrderedQueryable<TEntity>) Queryable.OrderBy(
myCollection,
(dynamic) access);

Resources