I want my repsository to be independent of the data access technology. Currently I am working on a Xamrin.Forms App that uses Azure Mobile App Services for data access. For performance and flexibility reasons I want my repository to look simmilar like the following:
Task<IEnumerable<IDomainObject>> GetDomainObjectAsync(Func<IQueryable<IDomainObject>, IQueryable<IDomainObject>> query)
Suppose my IDomainObject looks like the following:
public interface IDomainObject
{
string Name { get; }
}
and my DataAccess Object:
internal class AzureDomainObject : IDomainObject
{
public string Name { get; set; }
public string Id { get; set; }
}
As far as I found out and tested I can do the following to query the database within my repository implementation:
public async Task<IEnumerable<IDomainObject>> GetDomainObjectAsync(Func<IQueryable<IDomainObject>, IQueryable<IDomainObject>> query)
{
// _table of type IMobileServiceTable<AzureDomainObject> gotten by MobileServiceClient
var tableQuery = _table.GetQuery();
tableQuery.Query = tableQuery.Query.Take(4); // 1) this was for testing and it works (ordering etc also works)
// tableQuery.Query = query(tableQuery.Query); // 2) this was my initial idea how to use the input param
await _table.ReadAsync(tableQuery);
}
My poblem now is how to use the input param query to replace 1) with 2).
tableQuery.Query expects an IQueryable<AzureDomainObject> but query is of type IQueryable<IDomainObject>.
Neither .Cast<AzureDomainObject>() nor .OfType<AzureDomainObject>() work to convert. Nor does (IQueryable<IAzureDomainObject>)query; work.
Cast and OfType throw NotSupportedException and the hard cast throws an InvalidCastException.
I also tried to extract the Expression from the query input param and assign it to the tableQuery.Query. But then a runtime exception occurs that they are not compatible.
Another idea I had was to use the ReadAsync(string) overload and pass the string representation of the passed query param. But this way I don't know how to generate the string.
So the final question is: Does anyone knows how to hide both AzureDomainObject and IMobileServiceTable from the domain model but keep the flexibility and performance benefits of IQueryable in the repository interface?
According to your description, I checked this issue and here is my implementation for this scenario, you could refer to them.
Model:
public class TodoItem : IDomainObject
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
public interface IDomainObject
{
string Id { get; set; }
}
Repository:
public interface IAzureCloudTableRepository<T> where T : IDomainObject
{
Task<IEnumerable<T>> GetDomainObjectAsync(Func<IQueryable<T>, IQueryable<T>> query);
}
public class AzureCloudTableRepository<T> : IAzureCloudTableRepository<T> where T : IDomainObject
{
IMobileServiceTable<T> table;
public AzureCloudTableRepository(MobileServiceClient client)
{
this.table = client.GetTable<T>();
}
public async Task<T> CreateItemAsync(T item)
{
await table.InsertAsync(item);
return item;
}
public async Task<IEnumerable<T>> GetDomainObjectAsync(Func<IQueryable<T>, IQueryable<T>> query)
{
var tableQuery = this.table.CreateQuery();
tableQuery.Query = tableQuery.Query.Take(4); //the internal fixed query
tableQuery.Query = query(tableQuery.Query); //the external query
return await tableQuery.ToEnumerableAsync();
}
}
TEST:
var mobileService = new MobileServiceClient("https://{your-app-name}.azurewebsites.net");
var todoitem = new AzureCloudTableRepository<TodoItem>(mobileService);
var items = await todoitem.GetDomainObjectAsync((query) =>
{
return query.Where(q => q.Text!=null);
});
Related
I'm developing an Azure Mobile App service to interface to my Xamarin application.
I've created, connected and successfully populated an SQL Database, but when I try to add some filters to my request, for example an orderby() or where() clauses, it returns me a Bad Request error.
For example, this request: https://myapp.azurewebsites.net/tables/Race?$orderby=iRound%20desc,iYear%20desc&$top=1&ZUMO-API-VERSION=2.0.0 gives me {"message":"The query specified in the URI is not valid. Could not find a property named 'IYear' on type 'MyType'."}.
My configuration method is this:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.AddTablesWithEntityFramework()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
Database.SetInitializer(new CreateDatabaseIfNotExists<MainDataContext>());
app.UseWebApi(config);
and my DbContext is this:
public class MainDataContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString";
public MainDataContext() : base(connectionStringName)
{
Database.Log = s => WriteLog(s);
}
public void WriteLog(string msg)
{
System.Diagnostics.Debug.WriteLine(msg);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public DbSet<Race> Race { get; set; }
public DbSet ...ecc...
}
Following this guide, I added a migration after creating my TableControllers. So the TableController for the example type shown above is pretty standard:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public class RaceController : TableController<Race>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MainDataContext context = new MainDataContext();
DomainManager = new EntityDomainManager<Race>(context, Request);
}
// GET tables/Race
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Race> GetAllRace()
{
return Query();
}
// GET tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Race> GetRace(string id)
{
return Lookup(id);
}
// PATCH tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Race> PatchRace(string id, Delta<Race> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Race
public async Task<IHttpActionResult> PostRace(Race item)
{
Race current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteRace(string id)
{
return DeleteAsync(id);
}
}
As you can see, I already tried to add the EnableQuery attribute to my TableController, as seen on Google. I also tried to add these filters to the HttpConfiguration object, without any success:
config.Filters.Add(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
config.AddODataQueryFilter(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
I don't know what to investigate more, as things seems to be changing too fast for a newbie like me who's first got into Azure.
EDIT
I forgot to say that asking for the complete table, so for example https://myapp.azurewebsites.net/tables/Race?ZUMO-API-VERSION=2.0.0, returns correctly the entire dataset. The problem occurs only when adding some clauses to the request.
EDIT 2
My model is like this:
public class Race : EntityData
{
public int iRaceId { get; set; }
public int iYear { get; set; }
public int iRound { get; set; }
ecc..
}
and the database table that was automatically created is this, including all the properties inherited from EntityData:
Database table schema
Digging into the source code, Azure Mobile Apps sets up camelCase encoding of all requests and responses. It then puts them back after transmission accordign to rules - so iRaceId becomes IRaceId on the server.
The easiest solution to this is to bypass the auto-naming and use a JsonProperty attribute on each property within your server-side DTO and client-side DTO so that they match and will get encoding/decoded according to your rules.
So:
public class Race : EntityData
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("raceId")]
public int iRaceId { get; set; }
[JsonProperty("year")]
public int iYear { get; set; }
[JsonProperty("round")]
public int iRound { get; set; }
etc..
}
I have a boolean query that I want to add dynamically to using the object intializer syntax but I am having trouble doing so. Basically, I have 5 stages of checking parameters, and if they exist they get added to the boolquery. Here's what I am trying to do (obviously doesn't work):
SomeBoolQuery.Must.ToList().Add(someQueryContainer);
How can I make the above work so that I can dynamically add queries to the BoolQuery? No, I can't do it via this:
SomeBoolQuery.Must = new QueryContainer[] {query1, query2};
Because I don't know how many queries I am going to have and I can't add them all at once. I need a dynamic solution.
I was thinking maybe this:
SomeBoolQueryContainer &= someQuery;
and then at the end:
SomeBoolQuery.Must = new QueryContainer[] {someBoolQueryContainer};
But that seems a little redundant to say the least. Any ideas?
EDIT: The last option tried above doesn't seem to work. not returning any results.
Adding bool query dynamically using Fluent API :
ElasticQueryContainer elasticQueryContainer = GetSearchQuery(searchRequest);
var response = client.Search<IDocument>(s1 => s1
.Query(q => q
.Bool(bq => bq
.Should(elasticQueryContainer.orQuery.ToArray())
.Must(elasticQueryContainer.andQuery.ToArray())
.MustNot(elasticQueryContainer.notQuery.ToArray())
)));
public class ElasticQueryContainer
{
public List<QueryContainer> orQuery { get; set; }
public List<QueryContainer> andQuery { get; set; }
public List<QueryContainer> notQuery { get; set; }
public ElasticQueryContainer()
{
orQuery = new List<QueryContainer>();
andQuery = new List<QueryContainer>();
notQuery = new List<QueryContainer>();
}
}
public ElasticQueryContainer GetSearchQuery(SearchRequest searchRequest )
{
//...Populate ElasticQueryContainer : orQuery, andQuery, notQuery dynamically
var elasticQueryContainer = new ElasticQueryContainer();
if (!String.IsNullOrEmpty(searchRequest.FullTextSearch))
{
elasticQueryContainer.andQuery.Add(new QueryStringQuery
{
Query = searchRequest.FullTextSearch.ToLower(),
DefaultOperator = searchRequest.FullTextOperator == SearchEnums.FullTextSearchOperator.AND ? Operator.And : Operator.Or
});
}
}
The SearchRequest is a class having properties which has to be searched.
public class SearchRequest
{
public String FullTextSearch { get; set; }
public SearchEnums.FullTextSearchOperator FullTextOperator { get; set; }
}
For Object intializer syntax, you can use it this way :
new BoolQuery()
{
MustNot = elasticQueryContainer.notQuery.ToArray(),
Should = elasticQueryContainer.orQuery.ToArray(),
Must = elasticQueryContainer.andQuery.ToArray()
};
You can also refer to : https://www.elastic.co/guide/en/elasticsearch/client/net-api/2.x/bool-query-usage.html
I am new at automapper and it is a very good stuff easy to use, but now I have a problem with it. Trying to convert my derived class to base and it gives me
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types: ClientEventDb -> EventId
Database.ClientEventDb -> EventId
Destination path: ClientEvent
Source value:
Event:Login
Automapper wants to convert ClientEventDb to EventId? I don't understand why. EventId is an enum...
Please help me I have run out of ideas.
Here is the code which I run:
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>();
Console.WriteLine("hello");
return edbl.Select(edb => Mapper.Map<ClientEvent>(edb)).ToArray();
Here are my classes
[Table("events", Schema = "public")]
public class ClientEventDb : ClientEvent
{
public ClientEventDb(string userName, EventId happening, object userObject = null)
: base(userName, happening, userObject)
{
}
public ClientEventDb()
{
}
}
[ProtoContract]
[Table("events", Schema = "public")]
public class ClientEvent : ClientEventBase
{
[ProtoMember(1)]
[Column("username")]
public string UserName { get; private set; }
[ProtoMember(2)]
[Column("time")]
public DateTime DateTime { get; private set; }
[ProtoMember(3)]
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; private set; }
[ProtoMember(4)]
[Column("data")]
public byte[] UserObject { get; set; }
public ClientEvent(string userName,EventId happening, object userObject=null) : base(happening)
{
UserName = userName;
DateTime = DateTime.Now;
//UserObject = null;
if (userObject!=null) throw new NotImplementedException();
}
public ClientEvent()
{
}
protected ClientEvent Clone()
{
return (ClientEvent)MemberwiseClone();
}
}
[ProtoContract]
[ProtoInclude(10, typeof(ClientEvent))]
public class ClientEventBase
{
[Column("eventid")]
[ProtoMember(1)]
public int EventIdValue { get; set; } //must be public because of entity framework
[NotMapped]
public EventId EventId
{
get { return (EventId) EventIdValue; }
set { EventIdValue = (int) value; }
}
public ClientEventBase(EventId eventId)
{
EventId = eventId;
}
public ClientEventBase()
{
}
public override string ToString()
{
return String.Format("Event:{0}",EventId);
}
}
public enum EventId
{
Login = 1,
Logout,
ExitApplication,
}
UPDATE
bugfix: ClientEvent [Key] attribute moved to id property
Solution was this (thx to stuartd):
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>().ConstructUsing((ClientEventDb src) => new ClientEvent());
return edbl.Select(Mapper.Map<ClientEvent>).ToArray();
AutoMapper is confused as its made to map between similar properties in different classes, you are using it incorrectly - you just need to go from the derived class to the base which does not require AutoMapper. You could use this to do what you need....
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
return edbl.Cast<ClientEvent>().ToList();
I'd be looking at why you even feel you need a derived ClientEventDb though - understand we dont have the whole picture here but it seems to do nothing in addition to what the base class already does.
The issue is that ClientEvent has two constructors but you have not told AutoMapper which to use.
If you want it to use your constructor with parameters, change your mapping code to this and it will work:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing(src => new ClientEvent(src.UserName, src.EventId));
Or to make AutoMapper use the default constructor:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing((ClientEventDb src) => new ClientEvent());
I'm working in Entity Framework 5 and having problems creating an expression to use inside a method.
I believe the problem is that normally I would call the expression in a lambda expression such as dbContext.Counties.Select(GetLargeCities()), but in the code I am working with, I am projecting the Counties entity into a view model called CountyWithCities. Where I would normally call the expression, I have a singleton c and don't know how to call the expression there.
The reason I want to accomplish this using an expression is because I want the GetCountiesWithCities method to hit the database once, with Entity Framework constructing a complex graph for all the objects in the result.
For some reason the code below is producing the error `The name 'GetLargeCities' does not exist in the current context."
public class CountyWithCities // this is a view model
{
public int CountyID { get; set; }
public string CountyName { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class City // this is an entity
{
public int CityID { get; set; }
public string CityName { get; set; }
public int Population { get; set; }
}
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.Select(c => new CountyWithCities
{
CountyID = c.CountyID,
CountyName = c.CountyName,
Cities = GetLargeCities(c) // How do I call the expression here?
});
}
public Expression<Func<County, IEnumerable<City>>> GetLargeCities = county =>
county.Cities.Where(city => city.Population > 50000);
Thanks!
I normally do this with an extension method.
public static IQueriable<City> LargeCities(this IQueriable<County> counties){
return counties
.SelectMany(county=>county.Cities.Where(c=>c.Population > 50000));
}
usage:
dbContext.Counties.LargeCities()
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.LargeCities()
.GroupBy(c=>c.County)
.Select(c => new CountyWithCities
{
CountyID = g.Key.CountyID,
CountyName = g.Key.CountyName,
Cities = g.AsQueriable() // i cant remember the exact syntax here but you need the list from the group
});
}
I'm new with orchard.
To learn orchard module development, I am following documentation to try to create a commerce module.
The module consists of product part and product type, which has product part.
When I try to save data in following method:
public ActionResult Create(FormCollection input)
{
var product = contentManager.New<ProductPart>("Product");
product.Description = input["Description"];
product.Sku = input["Sku"];
product.Price =Convert.ToDecimal(input["Price"]);
if (!ModelState.IsValid)
{
return View(product);
}
contentManager.Create(product);
return RedirectToAction("Index");
}
I am getting an error that specific cast is Invalid and part(ContentPart) is null.
public static T New<T>(this IContentManager manager, string contentType)
where T : class, IContent {
var contentItem = manager.New(contentType);
if (contentItem == null)
return null;
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
return part;
}
I used content type Product and I have ProductRecord class for storage data, as below:
public class ProductRecord:ContentPartRecord
{
// public virtual int Id { get; set; }
public virtual string Sku { get; set; }
public virtual string Description { get; set; }
public virtual decimal Price { get; set; }
}
public class ProductPart : ContentPart<ProductRecord>
{
/*
public int Id
{
get { return Record.Id; }
set{Record.Id = value;}
}
*/
[Required]
public string Sku
{
get { return Record.Sku; }
set { Record.Sku = value; }
}
[Required]
public string Description
{
get { return Record.Description; }
set{ Record.Description = value;}
}
[Required]
public decimal Price
{
get { return Record.Price; }
set { Record.Price = value; }
}
}
Can anybody tell me what my problem is?
I'm just guessing, but did you declare your record and your ContentType in migration.cs? If you didn't, the content management will be unable to create a content item with your type as it will not know the type in question.
Your migration.cs should look somehow like that:
public class Migrations : DataMigrationImpl
{
public int Create()
{
SchemaBuilder.CreateTable("ProductRecord",
table =>
{
table.ContentPartRecord()
.Column<string>("Sku")
.Column<string>("Description")
.column<decimal>("Price");
});
ContentDefinitionManager.AlterTypeDefinition("Product", cfg => cfg.WithPart("ProductPart"));
return 1;
}
}
On a side note, the naming convention in Orchard is to name the record for a part XXXPartRecord. I don't think your problem lies there though.
I have mentioned this in you other thread.. Orchard Content Type is null
you need
Migrations
public class Migrations : DataMigrationImpl {
public int Create() {
SchemaBuilder.CreateTable("ProductRecord",
table => table
.ContentPartRecord()
.COLUMNS NEED TO BE SPECIFIED
);
ContentDefinitionManager.AlterTypeDefinition("Forum",
cfg => cfg
.WithPart("ProductPart")
.WithPart("CommonPart")
);
Repository
public class ProductPartHandler : ContentHandler {
public ProductPartHandler(IRepository repository) {
Filters.Add(StorageFilter.For(repository));
}
Hope this helps
You could try generating a similar part using the command line utility by pszmyd and see whats different.
http://www.szmyd.com.pl/blog/generating-orchard-content-parts-via-command-line