Xamarin realm autoincrement - xamarin

I'm trying to change from SQLite to Realm.io in my Xamarin projects, but can't find any autoincrement on ID's. I found a post with Java, with following line:
int nextID = (int) (realm.where(dbObj.class).maximumInt("id") + 1);
In Xamarin there isn't a where, but i tried this:
realm.All<DebitorPlateDBModel> ().Max (x => x.Id + 1);
Sadly "Max" isn't support.
Has anyone succeed on this?

There are different ways of achieving this, it just depends on what fits your model the best, here are a just a couple:
Test Model:
public class IdIntKeyModel : RealmObject
{
[Indexed]
public int ID { get; set; }
public string Humanized { get; set; }
}
Gap-less key ordering (via Count):
Note: Good for initial bulk imports
Note: Assumes only one thread adding records and you do not have gaps in your record ids, i.e. no deletes without reordering keys, etc...
var config = RealmConfiguration.DefaultConfiguration;
config.SchemaVersion = 1;
using (var theRealm = Realm.GetInstance("StackoverFlow.realm"))
{
var key = theRealm.All<IdIntKeyModel>();
theRealm.Write(() =>
{
for (int i = 1; i < 1000; i++)
{
var model = theRealm.CreateObject<IdIntKeyModel>();
model.ID = key.Count() + 1;
model.Humanized = model.ID.ToWords();
System.Diagnostics.Debug.WriteLine($"{model.ID} : {model.Humanized}");
}
});
var whatIsTheKey = theRealm.All<IdIntKeyModel>().OrderBy(modelKey => modelKey.ID).Last();
System.Diagnostics.Debug.WriteLine($"{whatIsTheKey.ID} : {whatIsTheKey.Humanized}");
}
Gap'ie key ordering (refetch the last record by indexed ID):
Note: "Gap'ie" is Trademark pending ;-)
var rand = new Random();
var config = RealmConfiguration.DefaultConfiguration;
config.SchemaVersion = 1;
using (var theRealm = Realm.GetInstance("StackOverflow.realm"))
{
theRealm.Write(() =>
{
for (int i = 1; i < 1000; i++)
{
var lastID = theRealm.All<IdIntKeyModel>().OrderByDescending(modelKey => modelKey.ID).FirstOrDefault();
var model = theRealm.CreateObject<IdIntKeyModel>();
model.ID = lastID != null ? lastID.ID + rand.Next(10) : 1; // use lastID.ID++ for normal code flow, using rand.Next as a test to check ID indexing
model.Humanized = model.ID.ToWords();
}
});
var lastKey = theRealm.All<IdIntKeyModel>().OrderBy(modelKey => modelKey.ID).Last();
System.Diagnostics.Debug.WriteLine($"{lastKey.ID} : {lastKey.Humanized}");
}
Note: Code updates based on added support for FirstOrDefault, tested w/ v0.78.1

"In Xamarin there isn't a where" is not correct - we support LINQ as you can see in the snippets on the home page.
However, you are correct that we don't (yet) have an auto-increment or anything for that role.
We will get something at some point, but due to synchronisation issues it will not be auto-increment, but rather something like auto-unique-id.
We just released the full Mobile Platform with sync (Xamarin is getting there). One of the big deals of the Realm Object Server is dealing with people who are editing offline data and then having highly reliable synchronisation to other Realms.
There is no way that simple auto-increment can be made to work with disconnected data creation (the first time I dealt with this was on a Mac back in 1996 but the laws of physics haven't changed, we just stopped using floppy disks).

where clause is actually supported by Realm. You only need to import linq. However, auto increment id is really a big deal.
I solved auto increment issue by creating my own id
using Realms;
using System;
namespace RealmDatabase
{
public class RealmUserObject : RealmObject
{
[PrimaryKey]
public int userID { get; set; }
public string userLoginName { get; set; }
public DateTimeOffset userCreated { get; set; }
public bool userActive { get; set; }
}
}
and then when adding account, i'm getting last user info from realm then get the last id from it ( which is int ) then + 1 before to insert new account.
public List<RealmUserObject> getAllUserAccountsFromDatabase()
{
try
{
realm = Realm.GetInstance(config);
return realm.All<RealmUserObject>().Last();
}
catch (Exception) { throw; }
}
i am calling whole account because it is useful for me in other scenario. but you can actually ask directly what you want like this way
return realm.All<RealmUserObject>().Last().userID;
note: ofcourse the issue with this is what if you don't have any record existing, then just insert it and make id initial to 1 and put else if account is greater than 0

Related

Entity framework 6 code first Many to many insert slow

I am trying to find a way to improve insert performances with the following code (please, read my questions after the code block):
//Domain classes
[Table("Products")]
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Sku { get; set; }
[ForeignKey("Orders")]
public virtual ICollection<Order> Orders { get; set; }
public Product()
{
Orders = new List<Order>();
}
}
[Table("Orders")]
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public decimal Total { get; set; }
[ForeignKey("Products")]
public virtual ICollection<Product> Products { get; set; }
public Order()
{
Products = new List<Product>();
}
}
//Data access
public class MyDataContext : DbContext
{
public MyDataContext()
: base("MyDataContext")
{
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Database.SetInitializer(new CreateDatabaseIfNotExists<MyDataContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<Order>().ToTable("Orders");
}
}
//Service layer
public interface IServices<T, K>
{
T Create(T item);
T Read(K key);
IEnumerable<T> ReadAll(Expression<Func<IEnumerable<T>, IEnumerable<T>>> pre);
T Update(T item);
void Delete(K key);
void Save();
void Dispose();
void BatchSave(IEnumerable<T> list);
void BatchUpdate(IEnumerable<T> list, Action<UpdateSpecification<T>> spec);
}
public class BaseServices<T, K> : IDisposable, IServices<T, K> where T : class
{
protected MyDataContext Context;
public BaseServices()
{
Context = new MyDataContext();
}
public T Create(T item)
{
T created;
created = Context.Set<T>().Add(item);
return created;
}
public void Delete(K key)
{
var item = Read(key);
if (item == null)
return;
Context.Set<T>().Attach(item);
Context.Set<T>().Remove(item);
}
public T Read(K key)
{
T read;
read = Context.Set<T>().Find(key);
return read;
}
public IEnumerable<T> ReadAll(Expression<Func<IEnumerable<T>, IEnumerable<T>>> pre)
{
IEnumerable<T> read;
read = Context.Set<T>().ToList();
read = pre.Compile().Invoke(read);
return read;
}
public T Update(T item)
{
Context.Set<T>().Attach(item);
Context.Entry<T>(item).CurrentValues.SetValues(item);
Context.Entry<T>(item).State = System.Data.Entity.EntityState.Modified;
return item;
}
public void Save()
{
Context.SaveChanges();
}
}
public interface IOrderServices : IServices<Order, int>
{
//custom logic goes here
}
public interface IProductServices : IServices<Product, int>
{
//custom logic goes here
}
//Web project's controller
public ActionResult TestCreateProducts()
{
//Create 100 new rest products
for (int i = 0; i < 100; i++)
{
_productServices.Create(new Product
{
Sku = i.ToString()
});
}
_productServices.Save();
var products = _productServices.ReadAll(r => r); //get a list of saved products to add them to orders
var random = new Random();
var orders = new List<Order>();
var count = 0;
//Create 3000 orders
for (int i = 1; i <= 3000; i++)
{
//Generate a random list of products to attach to the current order
var productIds = new List<int>();
var x = random.Next(1, products.Count() - 1);
for (int j = 0; j < x; j++)
{
productIds.Add(random.Next(products.Min(r => r.Id), products.Max(r => r.Id)));
}
//Create the order
var order = new Order
{
Title = "Order" + i,
Total = i,
Products = products.Where(p => productIds.Contains(p.Id))
};
orders.Add(order);
}
_orderServices.CreateRange(orders);
_orderServices.Save();
return RedirectToAction("Index");
}
This code works fine but is very VERY slow when the SaveChanges is executed.
Behind the scene, the annotations on the domain objects creates all the relationships needed: a OrderProducts table with the proper foreign keys are automatically created and the inserts are being done by EF properly.
I've tried many things with bulk inserts using EntityFramework.Utilities, SqlBulkCopy, etc... but none worked.
Is there a way to achieve this?
Understand this is only for testing purposes and my goal is to optimize the best I can any operations in our softwares using EF.
Thanks!
Just before you do your inserts disable your context's AutoDetectChangesEnabled (by setting it to false). Do your inserts and then set the AutoDetectChangesEnabled back to true e.g.;
try
{
MyContext.Configuration.AutoDetectChangesEnabled = false;
// do your inserts updates etc..
}
finally
{
MyContext.Configuration.AutoDetectChangesEnabled = true;
}
You can find more information on what this is doing here
I see two reasons why your code is slow.
Add vs. AddRange
You add entity one by one using the Create method.
You should always use AddRange over Add. The Add method will try to DetectChanges every time the add method is invoked while AddRange only once.
You should add a "CreateRange" method in your code.
public IEnumerable<T> CreateRange(IEnumerable<T> list)
{
return Context.Set<T>().AddRange(list);
}
var products = new List<Product>();
//Create 100 new rest products
for (int i = 0; i < 100; i++)
{
products.Add(new Product { Sku = i.ToString() });
}
_productServices.CreateRange(list);
_productServices.Save();
Disabling / Enabling the property AutoDetectChanges also work as #mark_h proposed, however personally I don't like this kind of solution.
Database Round Trip
A database round trip is required for every record to add, modify or delete. So if you insert 3,000 records, then 3,000 database round trip will be required which is VERY slow.
You already tried EntityFramework.BulkInsert or SqlBulkCopy, which is great. I recommend you first to try them again using the "AddRange" fix to see the newly performance.
Here is a biased comparison of library supporting BulkInsert for EF:
Entity Framework - Bulk Insert Library Reviews & Comparisons
Disclaimer: I'm the owner of the project Entity Framework Extensions
This library allows you to BulkSaveChanges, BulkInsert, BulkUpdate, BulkDelete and BulkMerge within your Database.
It supports all inheritances and associations.
// Easy to use
public void Save()
{
// Context.SaveChanges();
Context.BulkSaveChanges();
}
// Easy to customize
public void Save()
{
// Context.SaveChanges();
Context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
}
EDIT: Added answer to sub question
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker
The issue happens because you use two different DbContext. One for the product and one for order.
You may find a better answer than mine in a different thread like this answer.
The Add method successfully attach the product, subsequent call of the same product doesn't throw an error because it's the same product.
The AddRange method, however, attach the product multiple time since it's not come from the same context, so when Detect Changes is called, he doesn't know how to handle it.
One way to fix it is by re-using the same context
var _productServices = new BaseServices<Product, int>();
var _orderServices = new BaseServices<Order, int>(_productServices.Context);
While it may not be elegant, the performance will be improved.

Entity Framework, Code First and Full Text Search

I realize that a lot of questions have been asked relating to full text search and Entity Framework, but I hope this question is a bit different.
I am using Entity Framework, Code First and need to do a full text search. When I need to perform the full text search, I will typically have other criteria/restrictions as well - like skip the first 500 rows, or filter on another column, etc.
I see that this has been handled using table valued functions - see http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx. And this seems like the right idea.
Unfortunately, table valued functions are not supported until Entity Framework 5.0 (and even then, I believe, they are not supported for Code First).
My real question is what are the suggestions for the best way to handle this, both for Entity Framework 4.3 and Entity Framework 5.0. But to be specific:
Other than dynamic SQL (via System.Data.Entity.DbSet.SqlQuery, for example), are there any options available for Entity Framework 4.3?
If I upgrade to Entity Framework 5.0, is there a way I can use table valued functions with code first?
Thanks,
Eric
Using interceptors introduced in EF6, you could mark the full text search in linq and then replace it in dbcommand as described in http://www.entityframework.info/Home/FullTextSearch:
public class FtsInterceptor : IDbCommandInterceptor
{
private const string FullTextPrefix = "-FTSPREFIX-";
public static string Fts(string search)
{
return string.Format("({0}{1})", FullTextPrefix, search);
}
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
RewriteFullTextQuery(command);
}
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
RewriteFullTextQuery(command);
}
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}
public static void RewriteFullTextQuery(DbCommand cmd)
{
string text = cmd.CommandText;
for (int i = 0; i < cmd.Parameters.Count; i++)
{
DbParameter parameter = cmd.Parameters[i];
if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength))
{
if (parameter.Value == DBNull.Value)
continue;
var value = (string)parameter.Value;
if (value.IndexOf(FullTextPrefix) >= 0)
{
parameter.Size = 4096;
parameter.DbType = DbType.AnsiStringFixedLength;
value = value.Replace(FullTextPrefix, ""); // remove prefix we added n linq query
value = value.Substring(1, value.Length - 2);
// remove %% escaping by linq translator from string.Contains to sql LIKE
parameter.Value = value;
cmd.CommandText = Regex.Replace(text,
string.Format(
#"\[(\w*)\].\[(\w*)\]\s*LIKE\s*#{0}\s?(?:ESCAPE N?'~')",
parameter.ParameterName),
string.Format(#"contains([$1].[$2], #{0})",
parameter.ParameterName));
if (text == cmd.CommandText)
throw new Exception("FTS was not replaced on: " + text);
text = cmd.CommandText;
}
}
}
}
}
static class LanguageExtensions
{
public static bool In<T>(this T source, params T[] list)
{
return (list as IList<T>).Contains(source);
}
}
For example, if you have class Note with FTS-indexed field NoteText:
public class Note
{
public int NoteId { get; set; }
public string NoteText { get; set; }
}
and EF map for it
public class NoteMap : EntityTypeConfiguration<Note>
{
public NoteMap()
{
// Primary Key
HasKey(t => t.NoteId);
}
}
and context for it:
public class MyContext : DbContext
{
static MyContext()
{
DbInterception.Add(new FtsInterceptor());
}
public MyContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
}
public DbSet<Note> Notes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new NoteMap());
}
}
you can have quite simple syntax to FTS query:
class Program
{
static void Main(string[] args)
{
var s = FtsInterceptor.Fts("john");
using (var db = new MyContext("CONNSTRING"))
{
var q = db.Notes.Where(n => n.NoteText.Contains(s));
var result = q.Take(10).ToList();
}
}
}
That will generate SQL like
exec sp_executesql N'SELECT TOP (10)
[Extent1].[NoteId] AS [NoteId],
[Extent1].[NoteText] AS [NoteText]
FROM [NS].[NOTES] AS [Extent1]
WHERE contains([Extent1].[NoteText], #p__linq__0)',N'#p__linq__0 char(4096)',#p__linq__0='(john)
Please notice that you should use local variable and cannot move FTS wrapper inside expression like
var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john")));
I have found that the easiest way to implement this is to setup and configure full-text-search in SQL Server and then use a stored procedure. Pass your arguments to SQL, allow the DB to do its job and return either a complex object or map the results to an entity. You don't necessarily have to have dynamic SQL, but it may be optimal. For example, if you need paging, you could pass in PageNumber and PageSize on every request without the need for dynamic SQL. However, if the number of arguments fluctuates per query, it will be the optimal solution.
As the other guys mentioned, I would say start using Lucene.NET
Lucene has a pretty high learning curve, but I found an wrapper for it called "SimpleLucene", that can be found on CodePlex
Let me quote a couple of codeblocks from the blog to show you how easy it is to use. I've just started to use it, but got the hang of it really fast.
First, get some entities from your repository, or in your case, use Entity Framework
public class Repository
{
public IList<Product> Products {
get {
return new List<Product> {
new Product { Id = 1, Name = "Football" },
new Product { Id = 2, Name = "Coffee Cup"},
new Product { Id = 3, Name = "Nike Trainers"},
new Product { Id = 4, Name = "Apple iPod Nano"},
new Product { Id = 5, Name = "Asus eeePC"},
};
}
}
}
The next thing you want to do is create an index-definition
public class ProductIndexDefinition : IIndexDefinition<Product> {
public Document Convert(Product p) {
var document = new Document();
document.Add(new Field("id", p.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("name", p.Name, Field.Store.YES, Field.Index.ANALYZED));
return document;
}
public Term GetIndex(Product p) {
return new Term("id", p.Id.ToString());
}
}
and create an search index for it.
var writer = new DirectoryIndexWriter(
new DirectoryInfo(#"c:\index"), true);
var service = new IndexService();
service.IndexEntities(writer, Repository().Products, ProductIndexDefinition());
So, you now have an search-able index. The only remaining thing to do is.., searching! You can do pretty amazing things, but it can be as easy as this: (for greater examples see the blog or the documentation on codeplex)
var searcher = new DirectoryIndexSearcher(
new DirectoryInfo(#"c:\index"), true);
var query = new TermQuery(new Term("name", "Football"));
var searchService = new SearchService();
Func<Document, ProductSearchResult> converter = (doc) => {
return new ProductSearchResult {
Id = int.Parse(doc.GetValues("id")[0]),
Name = doc.GetValues("name")[0]
};
};
IList<Product> results = searchService.SearchIndex(searcher, query, converter);
The example here http://www.entityframework.info/Home/FullTextSearch is not complete solution. You will need to look into understand how the full text search works. Imagine you have a search field and the user types 2 words to hit search. The above code will throw an exception. You need to do pre-processing on the search phrase first to pass it to the query by using logical AND or OR.
for example your search phrase is "blah blah2" then you need to convert this into:
var searchTerm = #"\"blah\" AND/OR \"blah2\" ";
Complete solution would be:
value = Regex.Replace(value, #"\s+", " "); //replace multiplespaces
value = Regex.Replace(value, #"[^a-zA-Z0-9 -]", "").Trim();//remove non-alphanumeric characters and trim spaces
if (value.Any(Char.IsWhiteSpace))
{
value = PreProcessSearchKey(value);
}
public static string PreProcessSearchKey(string searchKey)
{
var splitedKeyWords = searchKey.Split(null); //split from whitespaces
// string[] addDoubleQuotes = new string[splitedKeyWords.Length];
for (int j = 0; j < splitedKeyWords.Length; j++)
{
splitedKeyWords[j] = $"\"{splitedKeyWords[j]}\"";
}
return string.Join(" AND ", splitedKeyWords);
}
this methods uses AND logic operator. You might pass that as an argument and use the method for both AND or OR operators.
You must escape none-alphanumeric characters otherwise it would throw exception when a user enters alpha numeric characters and you have no server site model level validation in place.
I recently had a similar requirement and ended up writing an IQueryable extension specifically for Microsoft full text index access, its available here IQueryableFreeTextExtensions

EF/Linq enum error

Given the following code:
public class RMAInfo
{
public enum RMAStatuses {
Undefined = 0, Approved = 1, Denied = 2,
Pending = 3, Received = 4, Closed = 5
}
public enum ReturnLocations { Undefined = 0, Utah = 1, Indy = 2 }
public RMAInfo()
{
ID = -1;
RMACode = string.Empty;
}
public int ID { get; set; }
public string RMACode { get; set; }
public string ResellerID { get; set; }
public RMAStatuses RMAStatus { get; set; }
}
private List<RMAInfo> GetRMAInfos_Internal(string resellerID)
{
List<RMAInfo> returnRMAInfos = new List<RMAInfo>();
using (Models.RMAEntities context = new Models.RMAEntities())
{
returnRMAInfos = (from r in context.RMAs
where r.ResellerID == resellerID
select new RMAInfo
{
ID = r.ID,
RMACode = r.RMACode,
ResellerID = r.ResellerID,
// error on next line!
RMAStatus = RMAInfo.RMAStatuses.Pending
}).ToList();
}
return returnRMAInfos;
}
I am getting an error on the assignment to the RMAStatus field. The error is
The specified value is not an instance of type 'Edm.Int32'
If I comment out that line, it works fine.
I have also tried to do this same code without using EF, and it seems to work fine.
Any ideas?
Entity Framework does not like the enum, as it cannot translate it to SQL. You would need to expose a way for EF to set the underlying int value, or you would have to set the value yourself once EF was done with it.
What you might do is expose an int property to set it. If you wish, you could restrict it to internal access so that perhaps callers can't see it but your EF code can (assuming callers are in different assemblies, but your context is not). Then you could have
public class RMAInfo
{
///<summary>
/// Integer representation of RMAStatus
///</summary>
internal int RMAStatusCode
{
get { return (int)this.RMAStatus; } // you could omit the getter
set { this.RMAStatus = (RMAInfo.RMAStatuses)value; }
}
}
...
select new RMAInfo
{
...
RMAStatusCode = (int)RMAInfo.RMAStatuses.Pending
}
To avoid this, you would basically select your RMAInfo sans status, and then iterate over the result to set each status to pending, leaving EF out of it entirely.
Installing .Net 4.5 appears to fix the issue as well (your project can still be on 4.0).
I was having this issue on our staging server (dev and test servers worked fine) and discovered that it did not have .Net 4.5 installed. Once I installed 4.5, the issue cleared up without any code changes.

Returning LINQ to Entities Query Result as JSON string

I'm attempting to construct a web service that allows for RESTful requests to return LINQ to Entities data as JSON string data. I have no problem executing a call to the database that returns one specific object:
public Product GetTicket(string s)
{
int id = Convert.ToInt32(s);
MWAEntities context = new MWAEntities();
var ticketEntity = (from p
in context.HD_TicketCurrentStatus
where p.Ticket_ID == id
select p).FirstOrDefault();
if (ticketEntity != null)
return TranslateTicketEntityToTicket(ticketEntity);
else
throw new Exception("Invalid Ticket ID");
/**
Product product = new Product();
product.TicketId = 1;
product.TicketDescription = "MyTest";
product.TicketOperator = "Chad Cross";
product.TicketStatus = "Work in Progress";
return product;
*/
}
private Product TranslateTicketEntityToTicket(
HD_TicketCurrentStatus ticketEntity)
{
Product ticket = new Product();
ticket.TicketId = ticketEntity.Ticket_ID;
ticket.TicketDescription = ticketEntity.F_PrivateMessage;
ticket.TicketStatus = ticketEntity.L_Status;
ticket.TicketOperator = ticketEntity.L_Technician;
return ticket;
}
Using curl, I get json data:
curl http://192.168.210.129:1111/ProductService/ticket/2
{"TicketDescription":"Firewall seems to be blocking her connection to www.rskco.com","TicketId":2,"TicketOperator":"Jeff","TicketStatus":"Completed"}
That being said, I have no idea how to get a string of JSON objects using the following query:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Assigned", "Unassigned" };
/**
* List of tickets with my userID
* */
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket(p.Ticket_ID, p.Ticket_CrtdUser, p.F_PrivateMessage, p.Ticket_CrtdDate, p.L_Status));
return ???;
}
MyTicket is a type defined as follows:
[DataContract]
public class MyTicket
{
public MyTicket(int ticketId, string TicketCreator, string FirstPrivateMessage, DateTime TicketCreatedDate, string Status)
{
this.TicketId = ticketId;
this.TicketCreator = TicketCreator;
this.FirstPrivateMessage = FirstPrivateMessage;
this.TicketCreatedDate = TicketCreatedDate;
this.Status = Status;
}
[DataMember]
public int TicketId { get; set; }
[DataMember]
public string TicketCreator { get; set; }
[DataMember]
public string FirstPrivateMessage { get; set; }
[DataMember]
public DateTime TicketCreatedDate { get; set; }
[DataMember]
public string Status { get; set; }
//p.Ticket_CrtdUser, p.Ticket_CrtdDate, p.Ticket_ID, p.F_PrivateMessage
}
I would just like to get a list of JSON strings as output in order to parse using JS. I've tried using a foreach loop to parse "var" into a List of MyTicket objects, calling .ToList()), etc., to no avail.
I cannot change the backend (SQL 2005/2008), but I'm trying to use a standard HTML/JS client to consume a .NET 4.0 web service. Any help would be greatly appreciated. I've spent literally days searching and reading books (especially on O'Reilly's Safari site) and I have not found a reasonable solution :(.
use Json.NET: http://james.newtonking.com/pages/json-net.aspx
using Newtonsoft.Json;
var serializer = new JsonSerializer();
serializer.Serialize(Response.Output, tickets); // per your example
EDIT: Argh, the above is if you want to handle the serialization yourself.
In your example, change the return of the method from List to Ticket[] and do
return tickets.ToArray();
I wanted to add that I eventually got help to solve this. I'm not using business entities even though I'm using the Entity Framework. This may not be a wise decision, but I'm increasingly confused with Linq2SQL and Linq2EF. Here is the the code that made the above work:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Created"};
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket{
TicketId = p.Ticket_ID,
TicketCreatedDate = p.Ticket_CrtdDate,
FirstPrivateMessage = p.F_PrivateMessage,
Status = p.L_Status,
TicketCreator = p.Ticket_CrtdUser
});
return tickets.ToList();
}

Why predicate isn't filtering when building it via reflection

I'm building a rather large filter based on an SearchObject that has 50+ fields that can be searched.
Rather than building my where clause for each one of these individually I thought I'd use some slight of hand and try building custom attribute suppling the necessary information and then using reflection to build out each of my predicate statements (Using LinqKit btw). Trouble is, that the code finds the appropriate values in the reflection code and successfully builds a predicate for the property, but the "where" doesn't seem to actually generate and my query always returns 0 records.
The attribute is simple:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public class FilterAttribute: Attribute
{
public FilterType FilterType { get; set; } //enum{ Object, Database}
public string FilterPath { get; set; }
//var predicate = PredicateBuilder.False<Metadata>();
}
And this is my method that builds out the query:
public List<ETracker.Objects.Item> Search(Search SearchObject, int Page, int PageSize)
{
var predicate = PredicateBuilder.False<ETracker.Objects.Item>();
Type t = typeof(Search);
IEnumerable<PropertyInfo> pi = t.GetProperties();
string title = string.Empty;
foreach (var property in pi)
{
if (Attribute.IsDefined(property, typeof(FilterAttribute)))
{
var attrs = property.GetCustomAttributes(typeof(FilterAttribute),true);
var value = property.GetValue(SearchObject, null);
if (property.Name == "Title")
title = (string)value;
predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);
}
}
var res = dataContext.GetAllItems().Take(1000)
.Where(a => SearchObject.Subcategories.Select(b => b.ID).ToArray().Contains(a.SubCategory.ID))
.Where(predicate);
return res.ToList();
}
The SearchObject is quite simple:
public class Search
{
public List<Item> Items { get; set; }
[Filter(FilterType = FilterType.Object, FilterPath = "Title")]
public string Title { get; set; }
...
}
Any suggestions will be greatly appreciated. I may well be going way the wrong direction and will take no offense if someone has a better alternative (or at least one that works)
You're not assigning your predicate anywhere. Change the line to this:
predicate = predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);

Resources